72 lines
2.3 KiB
OCaml
72 lines
2.3 KiB
OCaml
(* (c) 2019 Hannes Mehnert, all rights reserved *)
|
|
|
|
(* goal is to check a given zonefile whether it is valid (and to-be-used
|
|
by an authoritative NS - i.e. there must be a SOA record, TTL are good)
|
|
if a NS/MX name is within the zone, it needs an address record
|
|
the name of the file is taken as the domain name *)
|
|
open Dns
|
|
|
|
let ( let* ) = Result.bind
|
|
|
|
let load_zone zone =
|
|
let* data = Bos.OS.File.read Fpath.(v zone) in
|
|
let* rrs = Dns_zone.parse data in
|
|
let domain = Domain_name.of_string_exn Fpath.(basename (v zone)) in
|
|
let bad = Domain_name.Map.filter
|
|
(fun name _ -> not (Domain_name.is_subdomain ~domain ~subdomain:name))
|
|
rrs
|
|
in
|
|
if not (Domain_name.Map.is_empty bad) then
|
|
Error (`Msg (Fmt.str "Entries of domain '%a' are not in its zone, won't handle this:@.%a"
|
|
Domain_name.pp domain Dns.Name_rr_map.pp bad))
|
|
else
|
|
Ok (Dns_trie.insert_map rrs Dns_trie.empty)
|
|
|
|
let jump _ zone old =
|
|
let* trie = load_zone zone in
|
|
let* () =
|
|
Result.map_error
|
|
(fun e -> `Msg (Fmt.to_to_string Dns_trie.pp_zone_check e))
|
|
(Dns_trie.check trie)
|
|
in
|
|
Logs.app (fun m -> m "successfully checked zone") ;
|
|
let zones =
|
|
Dns_trie.fold Soa trie
|
|
(fun name _ acc -> Domain_name.Set.add name acc)
|
|
Domain_name.Set.empty
|
|
in
|
|
if Domain_name.Set.cardinal zones = 1 then
|
|
let zone = Domain_name.Set.choose zones in
|
|
let* zone_data = Dns_server.text zone trie in
|
|
Logs.debug (fun m -> m "assembled zone data %s" zone_data) ;
|
|
(match old with
|
|
| None -> Ok ()
|
|
| Some fn ->
|
|
let* old = load_zone fn in
|
|
match Dns_trie.lookup zone Soa trie, Dns_trie.lookup zone Soa old with
|
|
| Ok fresh, Ok old when Soa.newer ~old fresh ->
|
|
Logs.debug (fun m -> m "zone %a newer than old" Domain_name.pp zone) ;
|
|
Ok ()
|
|
| _ ->
|
|
Error (`Msg "SOA comparison wrong"))
|
|
else
|
|
Error (`Msg "expected exactly one zone")
|
|
|
|
open Cmdliner
|
|
|
|
let newzone =
|
|
let doc = "New zone file" in
|
|
Arg.(required & pos 0 (some file) None & info [] ~doc ~docv:"ZONE")
|
|
|
|
let oldzone =
|
|
let doc = "Old zone file" in
|
|
Arg.(value & opt (some file) None & info [ "old" ] ~doc ~docv:"ZONE")
|
|
|
|
let cmd =
|
|
let term =
|
|
Term.(term_result (const jump $ Dns_cli.setup_log $ newzone $ oldzone))
|
|
and info = Cmd.info "ozone" ~version:"10.2.2"
|
|
in
|
|
Cmd.v info term
|
|
|
|
let () = exit (Cmd.eval cmd)
|