%% Copyright (c) 2012-2020, DNSimple Corporation %% %% 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 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. %% Functions related to DNS records. -module(erldns_records). -include("erldns.hrl"). -include_lib("dns_erlang/include/dns.hrl"). -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). -endif. % Wildcard functions -export([ optionally_convert_wildcard/2, wildcard_qname/1 ]). % SOA TTL functions -export([ minimum_soa_ttl/2, rewrite_soa_ttl/1 ]). % Matcher functions -export([ match_name/1, match_type/1, match_name_and_type/2, match_types/1, match_wildcard/0, match_delegation/1, match_type_covered/1 ]). -export([ default_ttl/1, default_priority/1, name_type/1, root_hints/0, replace_name/1 ]). %% @doc If given Name is a wildcard name then the original qname needs to be returned in its place. -spec optionally_convert_wildcard(dns:dname(), dns:dname()) -> dns:dname(). optionally_convert_wildcard(Name, Qname) -> [Head | _] = dns:dname_to_labels(Name), case Head of <<"*">> -> Qname; _ -> Name end. %% @doc Get a wildcard variation of a Qname. Replaces the leading abel with an asterisk for wildcard lookup. -spec wildcard_qname(dns:dname()) -> dns:dname(). wildcard_qname(Qname) -> [_ | Rest] = dns:dname_to_labels(Qname), dns:labels_to_dname([<<"*">>] ++ Rest). %% @doc Return the TTL value or 3600 if it is undefined. -spec default_ttl(integer() | undefined) -> integer(). default_ttl(TTL) -> case TTL of undefined -> 3600; Value -> Value end. %% @doc Return the Priority value or 0 if it is undefined. -spec default_priority(integer() | undefined) -> integer(). default_priority(Priority) -> case Priority of undefined -> 0; Value -> Value end. %% @doc Applies a minimum TTL based on the SOA minimum value. %% %% The first argument is the Record that is being updated. %% The second argument is the SOA RR Data. -spec minimum_soa_ttl(dns:dns_rr(), dns:dns_rrdata_soa()) -> dns:dns_rr(). minimum_soa_ttl(Record, Data) when is_record(Data, dns_rrdata_soa) -> Record#dns_rr{ttl = erlang:min(Data#dns_rrdata_soa.minimum, Record#dns_rr.ttl)}; minimum_soa_ttl(Record, _) -> Record. %% @doc According to RFC 2308 the TTL for the SOA record in an NXDOMAIN response %% must be set to the value of the minimum field in the SOA content. -spec rewrite_soa_ttl(dns:message()) -> dns:message(). rewrite_soa_ttl(Message) -> rewrite_soa_ttl(Message, Message#dns_message.authority, []). rewrite_soa_ttl(Message, [], NewAuthority) -> Message#dns_message{authority = NewAuthority}; rewrite_soa_ttl(Message, [R | Rest], NewAuthority) -> rewrite_soa_ttl(Message, Rest, NewAuthority ++ [minimum_soa_ttl(R, R#dns_rr.data)]). %% Various matching functions. -spec match_name(dns:name()) -> fun((dns:rr()) -> boolean()). match_name(Name) -> fun(R) when is_record(R, dns_rr) -> R#dns_rr.name =:= Name end. -spec match_type(dns:type()) -> fun((dns:rr()) -> boolean()). match_type(Type) -> fun(R) when is_record(R, dns_rr) -> R#dns_rr.type =:= Type end. -spec match_name_and_type(dns:name(), dns:type()) -> fun((dns:rr()) -> boolean()). match_name_and_type(Name, Type) -> fun(R) when is_record(R, dns_rr) -> (R#dns_rr.name =:= Name) and (R#dns_rr.type =:= Type) end. -spec match_types([dns:type()]) -> fun((dns:rr()) -> boolean()). match_types(Types) -> fun(R) when is_record(R, dns_rr) -> lists:any(fun(T) -> R#dns_rr.type =:= T end, Types) end. -spec match_wildcard() -> fun((dns:rr()) -> boolean()). match_wildcard() -> fun(R) when is_record(R, dns_rr) -> lists:any(match_wildcard_label(), dns:dname_to_labels(R#dns_rr.name)) end. -spec match_wildcard_label() -> fun((binary()) -> boolean()). match_wildcard_label() -> fun(L) -> L =:= <<"*">> end. -spec match_delegation(dns:dname()) -> fun((dns:rr()) -> boolean()). match_delegation(Name) -> fun(R) when is_record(R, dns_rr) -> R#dns_rr.data =:= #dns_rrdata_ns{dname = Name} end. -spec match_type_covered(dns:type()) -> fun((dns:rr()) -> boolean()). match_type_covered(Qtype) -> fun(RRSig) -> RRSig#dns_rr.data#dns_rrdata_rrsig.type_covered =:= Qtype end. %% Replacement functions. replace_name(Name) -> fun(R) when is_record(R, dns_rr) -> R#dns_rr{name = Name} end. %% @doc Returns the type value given a binary string. -spec name_type(binary()) -> dns:type() | undefined. name_type(Type) when is_binary(Type) -> case Type of ?DNS_TYPE_A_BSTR -> ?DNS_TYPE_A_NUMBER; ?DNS_TYPE_NS_BSTR -> ?DNS_TYPE_NS_NUMBER; ?DNS_TYPE_MD_BSTR -> ?DNS_TYPE_MD_NUMBER; ?DNS_TYPE_MF_BSTR -> ?DNS_TYPE_MF_NUMBER; ?DNS_TYPE_CNAME_BSTR -> ?DNS_TYPE_CNAME_NUMBER; ?DNS_TYPE_SOA_BSTR -> ?DNS_TYPE_SOA_NUMBER; ?DNS_TYPE_MB_BSTR -> ?DNS_TYPE_MB_NUMBER; ?DNS_TYPE_MG_BSTR -> ?DNS_TYPE_MG_NUMBER; ?DNS_TYPE_MR_BSTR -> ?DNS_TYPE_MR_NUMBER; ?DNS_TYPE_NULL_BSTR -> ?DNS_TYPE_NULL_NUMBER; ?DNS_TYPE_WKS_BSTR -> ?DNS_TYPE_WKS_NUMBER; ?DNS_TYPE_PTR_BSTR -> ?DNS_TYPE_PTR_NUMBER; ?DNS_TYPE_HINFO_BSTR -> ?DNS_TYPE_HINFO_NUMBER; ?DNS_TYPE_MINFO_BSTR -> ?DNS_TYPE_MINFO_NUMBER; ?DNS_TYPE_MX_BSTR -> ?DNS_TYPE_MX_NUMBER; ?DNS_TYPE_TXT_BSTR -> ?DNS_TYPE_TXT_NUMBER; ?DNS_TYPE_RP_BSTR -> ?DNS_TYPE_RP_NUMBER; ?DNS_TYPE_AFSDB_BSTR -> ?DNS_TYPE_AFSDB_NUMBER; ?DNS_TYPE_X25_BSTR -> ?DNS_TYPE_X25_NUMBER; ?DNS_TYPE_ISDN_BSTR -> ?DNS_TYPE_ISDN_NUMBER; ?DNS_TYPE_RT_BSTR -> ?DNS_TYPE_RT_NUMBER; ?DNS_TYPE_NSAP_BSTR -> ?DNS_TYPE_NSAP_NUMBER; ?DNS_TYPE_SIG_BSTR -> ?DNS_TYPE_SIG_NUMBER; ?DNS_TYPE_KEY_BSTR -> ?DNS_TYPE_KEY_NUMBER; ?DNS_TYPE_PX_BSTR -> ?DNS_TYPE_PX_NUMBER; ?DNS_TYPE_GPOS_BSTR -> ?DNS_TYPE_GPOS_NUMBER; ?DNS_TYPE_AAAA_BSTR -> ?DNS_TYPE_AAAA_NUMBER; ?DNS_TYPE_LOC_BSTR -> ?DNS_TYPE_LOC_NUMBER; ?DNS_TYPE_NXT_BSTR -> ?DNS_TYPE_NXT_NUMBER; ?DNS_TYPE_EID_BSTR -> ?DNS_TYPE_EID_NUMBER; ?DNS_TYPE_NIMLOC_BSTR -> ?DNS_TYPE_NIMLOC_NUMBER; ?DNS_TYPE_SRV_BSTR -> ?DNS_TYPE_SRV_NUMBER; ?DNS_TYPE_ATMA_BSTR -> ?DNS_TYPE_ATMA_NUMBER; ?DNS_TYPE_NAPTR_BSTR -> ?DNS_TYPE_NAPTR_NUMBER; ?DNS_TYPE_KX_BSTR -> ?DNS_TYPE_KX_NUMBER; ?DNS_TYPE_CERT_BSTR -> ?DNS_TYPE_CERT_NUMBER; ?DNS_TYPE_DNAME_BSTR -> ?DNS_TYPE_DNAME_NUMBER; ?DNS_TYPE_SINK_BSTR -> ?DNS_TYPE_SINK_NUMBER; ?DNS_TYPE_OPT_BSTR -> ?DNS_TYPE_OPT_NUMBER; ?DNS_TYPE_APL_BSTR -> ?DNS_TYPE_APL_NUMBER; ?DNS_TYPE_DS_BSTR -> ?DNS_TYPE_DS_NUMBER; ?DNS_TYPE_CDS_BSTR -> ?DNS_TYPE_CDS_NUMBER; ?DNS_TYPE_SSHFP_BSTR -> ?DNS_TYPE_SSHFP_NUMBER; ?DNS_TYPE_IPSECKEY_BSTR -> ?DNS_TYPE_IPSECKEY_NUMBER; ?DNS_TYPE_RRSIG_BSTR -> ?DNS_TYPE_RRSIG_NUMBER; ?DNS_TYPE_NSEC_BSTR -> ?DNS_TYPE_NSEC_NUMBER; ?DNS_TYPE_DNSKEY_BSTR -> ?DNS_TYPE_DNSKEY_NUMBER; ?DNS_TYPE_CDNSKEY_BSTR -> ?DNS_TYPE_CDNSKEY_NUMBER; ?DNS_TYPE_NSEC3_BSTR -> ?DNS_TYPE_NSEC3_NUMBER; ?DNS_TYPE_NSEC3PARAM_BSTR -> ?DNS_TYPE_NSEC3PARAM_NUMBER; ?DNS_TYPE_DHCID_BSTR -> ?DNS_TYPE_DHCID_NUMBER; ?DNS_TYPE_HIP_BSTR -> ?DNS_TYPE_HIP_NUMBER; ?DNS_TYPE_NINFO_BSTR -> ?DNS_TYPE_NINFO_NUMBER; ?DNS_TYPE_RKEY_BSTR -> ?DNS_TYPE_RKEY_NUMBER; ?DNS_TYPE_TALINK_BSTR -> ?DNS_TYPE_TALINK_NUMBER; ?DNS_TYPE_SPF_BSTR -> ?DNS_TYPE_SPF_NUMBER; ?DNS_TYPE_UINFO_BSTR -> ?DNS_TYPE_UINFO_NUMBER; ?DNS_TYPE_UID_BSTR -> ?DNS_TYPE_UID_NUMBER; ?DNS_TYPE_GID_BSTR -> ?DNS_TYPE_GID_NUMBER; ?DNS_TYPE_UNSPEC_BSTR -> ?DNS_TYPE_UNSPEC_NUMBER; ?DNS_TYPE_TKEY_BSTR -> ?DNS_TYPE_TKEY_NUMBER; ?DNS_TYPE_TSIG_BSTR -> ?DNS_TYPE_TSIG_NUMBER; ?DNS_TYPE_IXFR_BSTR -> ?DNS_TYPE_IXFR_NUMBER; ?DNS_TYPE_AXFR_BSTR -> ?DNS_TYPE_AXFR_NUMBER; ?DNS_TYPE_MAILB_BSTR -> ?DNS_TYPE_MAILB_NUMBER; ?DNS_TYPE_MAILA_BSTR -> ?DNS_TYPE_MAILA_NUMBER; ?DNS_TYPE_ANY_BSTR -> ?DNS_TYPE_ANY_NUMBER; ?DNS_TYPE_CAA_BSTR -> ?DNS_TYPE_CAA_NUMBER; ?DNS_TYPE_DLV_BSTR -> ?DNS_TYPE_DLV_NUMBER; _ -> undefined end. -spec root_hints() -> {[dns:rr()], [dns:rr()]}. root_hints() -> { [ #dns_rr{ name = <<"">>, type = ?DNS_TYPE_NS, ttl = 518400, data = #dns_rrdata_ns{dname = <<"a.root-servers.net">>} }, #dns_rr{ name = <<"">>, type = ?DNS_TYPE_NS, ttl = 518400, data = #dns_rrdata_ns{dname = <<"b.root-servers.net">>} }, #dns_rr{ name = <<"">>, type = ?DNS_TYPE_NS, ttl = 518400, data = #dns_rrdata_ns{dname = <<"c.root-servers.net">>} }, #dns_rr{ name = <<"">>, type = ?DNS_TYPE_NS, ttl = 518400, data = #dns_rrdata_ns{dname = <<"d.root-servers.net">>} }, #dns_rr{ name = <<"">>, type = ?DNS_TYPE_NS, ttl = 518400, data = #dns_rrdata_ns{dname = <<"e.root-servers.net">>} }, #dns_rr{ name = <<"">>, type = ?DNS_TYPE_NS, ttl = 518400, data = #dns_rrdata_ns{dname = <<"f.root-servers.net">>} }, #dns_rr{ name = <<"">>, type = ?DNS_TYPE_NS, ttl = 518400, data = #dns_rrdata_ns{dname = <<"g.root-servers.net">>} }, #dns_rr{ name = <<"">>, type = ?DNS_TYPE_NS, ttl = 518400, data = #dns_rrdata_ns{dname = <<"h.root-servers.net">>} }, #dns_rr{ name = <<"">>, type = ?DNS_TYPE_NS, ttl = 518400, data = #dns_rrdata_ns{dname = <<"i.root-servers.net">>} }, #dns_rr{ name = <<"">>, type = ?DNS_TYPE_NS, ttl = 518400, data = #dns_rrdata_ns{dname = <<"j.root-servers.net">>} }, #dns_rr{ name = <<"">>, type = ?DNS_TYPE_NS, ttl = 518400, data = #dns_rrdata_ns{dname = <<"k.root-servers.net">>} }, #dns_rr{ name = <<"">>, type = ?DNS_TYPE_NS, ttl = 518400, data = #dns_rrdata_ns{dname = <<"l.root-servers.net">>} }, #dns_rr{ name = <<"">>, type = ?DNS_TYPE_NS, ttl = 518400, data = #dns_rrdata_ns{dname = <<"m.root-servers.net">>} } ], [ #dns_rr{ name = <<"a.root-servers.net">>, type = ?DNS_TYPE_A, ttl = 3600000, data = #dns_rrdata_a{ip = {198, 41, 0, 4}} }, #dns_rr{ name = <<"b.root-servers.net">>, type = ?DNS_TYPE_A, ttl = 3600000, data = #dns_rrdata_a{ip = {192, 228, 79, 201}} }, #dns_rr{ name = <<"c.root-servers.net">>, type = ?DNS_TYPE_A, ttl = 3600000, data = #dns_rrdata_a{ip = {192, 33, 4, 12}} }, #dns_rr{ name = <<"d.root-servers.net">>, type = ?DNS_TYPE_A, ttl = 3600000, data = #dns_rrdata_a{ip = {128, 8, 10, 90}} }, #dns_rr{ name = <<"e.root-servers.net">>, type = ?DNS_TYPE_A, ttl = 3600000, data = #dns_rrdata_a{ip = {192, 203, 230, 10}} }, #dns_rr{ name = <<"f.root-servers.net">>, type = ?DNS_TYPE_A, ttl = 3600000, data = #dns_rrdata_a{ip = {192, 5, 5, 241}} }, #dns_rr{ name = <<"g.root-servers.net">>, type = ?DNS_TYPE_A, ttl = 3600000, data = #dns_rrdata_a{ip = {192, 112, 36, 4}} }, #dns_rr{ name = <<"h.root-servers.net">>, type = ?DNS_TYPE_A, ttl = 3600000, data = #dns_rrdata_a{ip = {128, 63, 2, 53}} }, #dns_rr{ name = <<"i.root-servers.net">>, type = ?DNS_TYPE_A, ttl = 3600000, data = #dns_rrdata_a{ip = {192, 36, 148, 17}} }, #dns_rr{ name = <<"j.root-servers.net">>, type = ?DNS_TYPE_A, ttl = 3600000, data = #dns_rrdata_a{ip = {192, 58, 128, 30}} }, #dns_rr{ name = <<"k.root-servers.net">>, type = ?DNS_TYPE_A, ttl = 3600000, data = #dns_rrdata_a{ip = {193, 0, 14, 129}} }, #dns_rr{ name = <<"l.root-servers.net">>, type = ?DNS_TYPE_A, ttl = 3600000, data = #dns_rrdata_a{ip = {198, 32, 64, 12}} }, #dns_rr{ name = <<"m.root-servers.net">>, type = ?DNS_TYPE_A, ttl = 3600000, data = #dns_rrdata_a{ip = {202, 12, 27, 33}} } ] }. -ifdef(TEST). wildcard_qname_test_() -> ?_assertEqual(<<"*.b.example.com">>, wildcard_qname(<<"a.b.example.com">>)). minimum_soa_ttl_test_() -> [ ?_assertMatch(#dns_rr{ttl = 3600}, minimum_soa_ttl(#dns_rr{ttl = 3600}, #dns_rrdata_a{})), ?_assertMatch(#dns_rr{ttl = 30}, minimum_soa_ttl(#dns_rr{ttl = 3600}, #dns_rrdata_soa{minimum = 30})), ?_assertMatch(#dns_rr{ttl = 30}, minimum_soa_ttl(#dns_rr{ttl = 30}, #dns_rrdata_soa{minimum = 3600})) ]. replace_name_test_() -> [ ?_assertEqual([], lists:map(replace_name(<<"example">>), [])), ?_assertMatch([#dns_rr{name = <<"example">>}], lists:map(replace_name(<<"example">>), [#dns_rr{name = <<"test.com">>}])) ]. match_name_test_() -> [ ?_assert(lists:any(match_name(<<"example.com">>), [#dns_rr{name = <<"example.com">>}])), ?_assertNot(lists:any(match_name(<<"example.com">>), [#dns_rr{name = <<"example.net">>}])) ]. match_type_test_() -> [ ?_assert(lists:any(match_type(?DNS_TYPE_A), [#dns_rr{type = ?DNS_TYPE_A}])), ?_assertNot(lists:any(match_type(?DNS_TYPE_CNAME), [#dns_rr{type = ?DNS_TYPE_A}])) ]. match_types_test_() -> [ ?_assert(lists:any(match_types([?DNS_TYPE_A]), [#dns_rr{type = ?DNS_TYPE_A}])), ?_assert(lists:any(match_types([?DNS_TYPE_A, ?DNS_TYPE_CNAME]), [#dns_rr{type = ?DNS_TYPE_A}])), ?_assertNot(lists:any(match_types([?DNS_TYPE_CNAME]), [#dns_rr{type = ?DNS_TYPE_A}])) ]. match_wildcard_test_() -> [ ?_assert(lists:any(match_wildcard(), [#dns_rr{name = <<"*.example.com">>}])), ?_assertNot(lists:any(match_wildcard(), [#dns_rr{name = <<"www.example.com">>}])) ]. match_delegation_test_() -> [ ?_assert(lists:any(match_delegation(<<"ns1.example.com">>), [#dns_rr{data = #dns_rrdata_ns{dname = <<"ns1.example.com">>}}])), ?_assertNot(lists:any(match_delegation(<<"ns1.example.com">>), [#dns_rr{data = #dns_rrdata_ns{dname = <<"ns2.example.com">>}}])) ]. match_wildcard_label_test_() -> [ ?_assert(lists:any(match_wildcard_label(), dns:dname_to_labels(<<"*.example.com">>))), ?_assertNot(lists:any(match_wildcard_label(), dns:dname_to_labels(<<"www.example.com">>))) ]. -endif.