148 lines
4.6 KiB
Elixir
148 lines
4.6 KiB
Elixir
defmodule Exdns.ZoneRules do
|
|
@moduledoc false
|
|
alias Exdns.DnsPacket.{ResourceRecord, RRType}
|
|
alias Exdns.DnsPacket.RRData.Types
|
|
def normalize_records(name, records, existing_records) do
|
|
serial = next_serial(existing_records)
|
|
|
|
records
|
|
|> ensure_soa(name, serial)
|
|
|> ensure_ns(name)
|
|
end
|
|
def find_authority(zone, labels) do
|
|
labels
|
|
|> suffixes()
|
|
|> Enum.reduce_while(:error, fn name, _ ->
|
|
records = Map.get(zone, name, [])
|
|
soa = filter_records(records, :soa)
|
|
ns = filter_records(records, :ns)
|
|
|
|
if soa != [] or ns != [] do
|
|
{:halt, {:ok, %{name: name, soa: soa, ns: ns}}}
|
|
else
|
|
{:cont, :error}
|
|
end
|
|
end)
|
|
end
|
|
def lookup_records(zone, name) do
|
|
name
|
|
|> suffixes()
|
|
|> Enum.reduce_while(:error, fn zone_name, _ -> lookup_in_zone(zone, name, zone_name) end)
|
|
end
|
|
def remove_records(records, to_remove) when is_list(records) and is_list(to_remove) do
|
|
remove_set = MapSet.new(to_remove)
|
|
Enum.reject(records, &MapSet.member?(remove_set, &1))
|
|
end
|
|
defp next_serial(records) do
|
|
case find_soa(records) do
|
|
%Types.SOA{serial: serial} when is_integer(serial) -> serial + 1
|
|
_ -> :os.system_time(:second)
|
|
end
|
|
end
|
|
defp ensure_soa(records, zone_name, serial) do
|
|
if Enum.any?(records, &soa_record?/1) do
|
|
Enum.map(records, fn
|
|
%ResourceRecord{rdata: %Types.SOA{} = soa} = rr ->
|
|
%ResourceRecord{rr | rdata: %Types.SOA{soa | serial: serial}}
|
|
|
|
rr ->
|
|
rr
|
|
end)
|
|
else
|
|
[default_soa(zone_name, serial) | records]
|
|
end
|
|
end
|
|
defp ensure_ns(records, zone_name) do
|
|
if Enum.any?(records, &ns_record?/1) do
|
|
records
|
|
else
|
|
[default_ns(zone_name) | records]
|
|
end
|
|
end
|
|
defp default_soa(zone_name, serial) do
|
|
%ResourceRecord{
|
|
name: zone_name,
|
|
type: :soa,
|
|
class: 1,
|
|
ttl: 3600,
|
|
rdata: %Types.SOA{
|
|
mname: default_ns_name(zone_name),
|
|
rname: default_rname(zone_name),
|
|
serial: serial,
|
|
refresh: 3600,
|
|
retry: 600,
|
|
expire: 1_209_600,
|
|
minimum: 300
|
|
}
|
|
}
|
|
end
|
|
defp default_ns(zone_name) do
|
|
%ResourceRecord{
|
|
name: zone_name,
|
|
type: :ns,
|
|
class: 1,
|
|
ttl: 3600,
|
|
rdata: %Types.NS{name: default_ns_name(zone_name)}
|
|
}
|
|
end
|
|
defp default_ns_name(zone_name), do: ["ns1" | zone_name]
|
|
defp default_rname(zone_name), do: ["hostmaster" | zone_name]
|
|
defp soa_record?(%ResourceRecord{rdata: %Types.SOA{}}), do: true
|
|
defp soa_record?(%ResourceRecord{} = rr), do: rr_type_atom(rr) == :soa
|
|
defp soa_record?(_), do: false
|
|
defp ns_record?(%ResourceRecord{rdata: %Types.NS{}}), do: true
|
|
defp ns_record?(%ResourceRecord{} = rr), do: rr_type_atom(rr) == :ns
|
|
defp ns_record?(_), do: false
|
|
defp find_soa(records) do
|
|
records
|
|
|> Enum.find(&soa_record?/1)
|
|
|> case do
|
|
%ResourceRecord{rdata: %Types.SOA{} = soa} -> soa
|
|
_ -> nil
|
|
end
|
|
end
|
|
defp filter_records(records, type) do
|
|
Enum.filter(records, fn rr -> rr_type_atom(rr) == type end)
|
|
end
|
|
|
|
defp pick_records(records, name, zone_name) do
|
|
direct = Enum.filter(records, fn rr -> rr.name == name end)
|
|
|
|
if direct != [] do
|
|
{:ok, direct}
|
|
else
|
|
wildcard = Enum.filter(records, fn rr -> rr.name == ["*" | zone_name] end)
|
|
if wildcard != [], do: {:ok, wildcard}, else: :error
|
|
end
|
|
end
|
|
|
|
defp lookup_in_zone(zone, name, zone_name) do
|
|
case Map.fetch(zone, zone_name) do
|
|
{:ok, records} ->
|
|
case pick_records(records, name, zone_name) do
|
|
{:ok, _} = ok -> {:halt, ok}
|
|
:error -> {:halt, :error}
|
|
end
|
|
|
|
:error ->
|
|
{:cont, :error}
|
|
end
|
|
end
|
|
defp rr_type_atom(%ResourceRecord{type: type}) when is_atom(type), do: type
|
|
defp rr_type_atom(%ResourceRecord{type: type}) when is_integer(type), do: RRType.from_code(type)
|
|
|
|
defp rr_type_atom(%ResourceRecord{rdata: %Types.A{}}), do: :a
|
|
defp rr_type_atom(%ResourceRecord{rdata: %Types.AAAA{}}), do: :aaaa
|
|
defp rr_type_atom(%ResourceRecord{rdata: %Types.CNAME{}}), do: :cname
|
|
defp rr_type_atom(%ResourceRecord{rdata: %Types.NS{}}), do: :ns
|
|
defp rr_type_atom(%ResourceRecord{rdata: %Types.TXT{}}), do: :txt
|
|
defp rr_type_atom(%ResourceRecord{rdata: %Types.MX{}}), do: :mx
|
|
defp rr_type_atom(%ResourceRecord{rdata: %Types.SRV{}}), do: :srv
|
|
defp rr_type_atom(%ResourceRecord{rdata: %Types.CAA{}}), do: :caa
|
|
defp rr_type_atom(%ResourceRecord{rdata: %Types.SOA{}}), do: :soa
|
|
defp rr_type_atom(_), do: nil
|
|
defp suffixes(labels) when is_list(labels) do
|
|
Enum.with_index(labels) |> Enum.map(fn {_item, index} -> Enum.drop(labels, index) end)
|
|
end
|
|
end
|