Packages
erldns
10.5.3
11.0.2
11.0.1
11.0.0
10.6.0
10.5.6
10.5.5
10.5.4
10.5.3
10.5.2
10.5.1
10.5.0
10.4.4
10.4.3
10.4.2
10.4.1
10.4.0
10.3.0
10.2.1
10.2.0
10.1.0
10.0.0
10.0.0-rc4
10.0.0-rc3
10.0.0-rc2
10.0.0-rc1
9.1.0
9.0.0
9.0.0-rc3
9.0.0-rc2
9.0.0-rc1
8.1.0
8.0.0
8.0.0-rc6
8.0.0-rc5
8.0.0-rc4
8.0.0-rc3
8.0.0-rc2
8.0.0-rc1
7.0.0
7.0.0-rc9
7.0.0-rc8
7.0.0-rc7
7.0.0-rc6
7.0.0-rc5
7.0.0-rc4
7.0.0-rc3
7.0.0-rc2
7.0.0-rc12
7.0.0-rc11
7.0.0-rc10
7.0.0-rc1
6.0.2
6.0.1
6.0.0
5.0.0
4.3.1
4.3.0
4.2.4
4.2.3
4.2.2
4.2.1
4.2.0
4.1.2
4.1.1
4.1.0
4.0.0
3.0.0
1.0.0
Erlang Authoritative DNS Server
Current section
Files
Jump to
Current section
Files
src/zones/erldns_zone_decoder.erl
-module(erldns_zone_decoder).
-moduledoc false.
-include_lib("dns_erlang/include/dns.hrl").
-include_lib("kernel/include/logger.hrl").
-include_lib("erldns/include/erldns.hrl").
-include_lib("public_key/include/public_key.hrl").
-define(LOG_METADATA, #{domain => [erldns, zones, decoder]}).
-export([decode/2, decode_record/2, parse_keysets/1]).
-ifdef(TEST).
-export([json_record_to_erlang/1]).
-endif.
-spec decode(json:decode_value(), [erldns_zone_codec:decoder()]) -> erldns:zone().
decode(#{~"name" := Name, ~"records" := JsonRecords} = Zone, Decoders) ->
Sha = maps:get(~"sha", Zone, ~""),
JsonKeys = maps:get(~"keys", Zone, []),
Records = lists:map(fun(JsonRecord) -> decode_record(JsonRecord, Decoders) end, JsonRecords),
FilteredRecords = lists:filter(record_filter(), Records),
DistinctRecords = lists:usort(FilteredRecords),
erldns_zone_codec:build_zone(Name, Sha, DistinctRecords, parse_keysets(JsonKeys)).
-spec decode_record(#{binary() => json:decode_value()}, [erldns_zone_codec:decoder()]) ->
not_implemented | dns:rr().
decode_record(JsonRecord, Decoders) ->
maybe
true ?= apply_context_options(JsonRecord),
not_implemented ?= json_record_to_erlang(JsonRecord),
not_implemented ?= try_custom_decoders(JsonRecord, Decoders),
?LOG_WARNING(#{what => unsupported_record, record => JsonRecord}, ?LOG_METADATA),
not_implemented
else
false ->
not_implemented;
Value ->
Value
end.
-spec parse_keysets([json:decode_value()]) -> [erldns:keyset()].
parse_keysets([]) ->
[];
parse_keysets(JsonKeys) ->
parse_keysets(JsonKeys, []).
% RFC4034: ยง3.1.5. Signature Expiration and Inception Fields
% The Signature Expiration and Inception field values specify a date
% and time in the form of a 32-bit unsigned number of seconds elapsed
% since 1 January 1970 00:00:00 UTC, ignoring leap seconds, in network
% byte order.
parse_keysets([], Keys) ->
Keys;
parse_keysets([Key | Rest], Keys) ->
KeySet =
#keyset{
key_signing_key = to_crypto_key(maps:get(~"ksk", Key)),
key_signing_key_tag = maps:get(~"ksk_keytag", Key),
key_signing_alg = maps:get(~"ksk_alg", Key),
zone_signing_key = to_crypto_key(maps:get(~"zsk", Key)),
zone_signing_key_tag = maps:get(~"zsk_keytag", Key),
zone_signing_alg = maps:get(~"zsk_alg", Key),
inception = calendar:rfc3339_to_system_time(
binary_to_list(maps:get(~"inception", Key)), [{unit, second}]
),
valid_until = calendar:rfc3339_to_system_time(
binary_to_list(maps:get(~"until", Key)), [{unit, second}]
)
},
parse_keysets(Rest, [KeySet | Keys]).
to_crypto_key(KeyBin) ->
DecodedKey = public_key:pem_entry_decode(lists:last(public_key:pem_decode(KeyBin))),
extract_key(DecodedKey).
extract_key(#'RSAPrivateKey'{publicExponent = E, modulus = M, privateExponent = N}) ->
[E, M, N];
extract_key(#'ECPrivateKey'{privateKey = Key, parameters = {namedCurve, ?'secp256r1'}}) ->
Key;
extract_key(#'ECPrivateKey'{privateKey = Key, parameters = {namedCurve, ?'secp384r1'}}) ->
Key;
extract_key(#'ECPrivateKey'{privateKey = Key, parameters = {namedCurve, ?'id-Ed25519'}}) ->
Key;
extract_key(#'ECPrivateKey'{privateKey = Key, parameters = {namedCurve, ?'id-Ed448'}}) ->
Key.
record_filter() ->
fun(R) -> R =/= not_implemented end.
%% Determine if a record should be used in this name server's context.
%%
%% If the context is undefined then the record will always be used.
%%
%% If the context is a list and has at least one condition that passes
%% then it will be included in the zone
-spec apply_context_options(dynamic()) -> boolean().
apply_context_options(#{~"context" := Context}) ->
case application:get_env(erldns, zones, #{}) of
#{context_options := ContextOptions} when is_map(ContextOptions) ->
apply_context_match_empty_check(
maps:get(match_empty, ContextOptions, false), Context
) orelse
apply_context_list_check(
maps:get(allow, ContextOptions, []), Context
);
_ ->
true
end;
apply_context_options(#{}) ->
true.
-spec apply_context_list_check(list(), list()) -> boolean().
apply_context_list_check(ContextAllow, Context) ->
ContextSet = sets:from_list(Context, [{version, 2}]),
ContextAllowSet = sets:from_list(ContextAllow, [{version, 2}]),
0 =/= sets:size(sets:intersection(ContextAllowSet, ContextSet)).
-spec apply_context_match_empty_check(true | dynamic(), [dynamic()]) -> boolean().
apply_context_match_empty_check(true, []) ->
true;
apply_context_match_empty_check(_, _) ->
false.
try_custom_decoders(_, []) ->
not_implemented;
try_custom_decoders(Data, [Decoder | Rest]) ->
case Decoder(Data) of
not_implemented ->
try_custom_decoders(Data, Rest);
Record ->
Record
end.
% Internal converters
-spec json_record_to_erlang(dynamic()) -> not_implemented | dns:rr() | badarg.
json_record_to_erlang(#{~"name" := _, ~"type" := _, ~"ttl" := _, ~"data" := Data} = JsonRecord) when
Data =/= null
->
try
RR = #dns_rr{} = dns_json:from_map(JsonRecord),
case RR#dns_rr.type of
?DNS_TYPE_DNSKEY -> dnssec:add_keytag_to_dnskey(RR);
?DNS_TYPE_CDNSKEY -> dnssec:add_keytag_to_cdnskey(RR);
_ -> RR
end
catch
error:{invalid_ipv4_in_json, _, _} = Reason:Stacktrace ->
erlang:raise(error, Reason, Stacktrace);
error:{invalid_ipv6_in_json, _, _} = Reason:Stacktrace ->
erlang:raise(error, Reason, Stacktrace);
error:{svcb_mandatory_validation_error, _} = Reason:Stacktrace ->
erlang:raise(error, Reason, Stacktrace);
Class:Reason:Stacktrace ->
?LOG_DEBUG(
#{
what => from_map_failed,
class => Class,
reason => Reason,
stacktrace => Stacktrace,
record => JsonRecord
},
?LOG_METADATA
),
not_implemented
end;
json_record_to_erlang(#{}) ->
not_implemented.