(* (c) 2019 Hannes Mehnert, all rights reserved *) open Dns let notify zone serial key now = let raw_zone = Domain_name.raw zone in let question = Packet.Question.create raw_zone Soa and soa = { Soa.nameserver = raw_zone ; hostmaster = raw_zone ; serial ; refresh = 0l; retry = 0l ; expiry = 0l ; minimum = 0l } and header = Randomconv.int16 Mirage_crypto_rng.generate, Packet.Flags.singleton `Authoritative in let p = Packet.create header question (`Notify (Some soa)) in match key with | None -> Ok (p, fst (Packet.encode `Tcp p), None) | Some (keyname, _, dnskey) -> Logs.debug (fun m -> m "signing with key %a: %a" Domain_name.pp keyname Dnskey.pp dnskey) ; match Dns_tsig.encode_and_sign ~proto:`Tcp p now dnskey keyname with | Ok (cs, mac) -> Ok (p, cs, Some mac) | Error e -> Error e let jump _ serverip port zone key serial = Mirage_crypto_rng_unix.use_default (); let now = Ptime_clock.now () in Logs.app (fun m -> m "notifying to %a:%d zone %a serial %lu" Ipaddr.pp serverip port Domain_name.pp zone serial) ; match notify zone serial key now with | Error s -> Error (`Msg (Fmt.str "signing %a" Dns_tsig.pp_s s)) | Ok (request, data, mac) -> let data_len = String.length data in Logs.debug (fun m -> m "built data %d" data_len) ; let socket = Dns_cli.connect_tcp serverip port in Dns_cli.send_tcp socket data ; let read_data = Dns_cli.recv_tcp socket in Unix.close socket ; match key with | None -> begin match Packet.decode read_data with | Ok reply -> begin match Packet.reply_matches_request ~request reply with | Ok `Notify_ack -> Logs.app (fun m -> m "successful notify!") ; Ok () | Ok r -> Error (`Msg (Fmt.str "expected notify ack, got %a" Packet.pp_reply r)) | Error e -> Error (`Msg (Fmt.str "notify reply %a is not ok %a" Packet.pp reply Packet.pp_mismatch e)) end | Error e -> Error (`Msg (Fmt.str "failed to decode notify reply! %a" Packet.pp_err e)) end | Some (keyname, _, dnskey) -> match Dns_tsig.decode_and_verify now dnskey keyname ?mac read_data with | Error e -> Error (`Msg (Fmt.str "failed to decode TSIG signed notify reply! %a" Dns_tsig.pp_e e)) | Ok (reply, _, _) -> match Packet.reply_matches_request ~request reply with | Ok `Notify_ack -> Logs.app (fun m -> m "successful TSIG signed notify!") ; Ok () | Ok r -> Error (`Msg (Fmt.str "expected notify ack, got %a" Packet.pp_reply r)) | Error e -> Error (`Msg (Fmt.str "expected reply to %a %a, got %a!" Packet.pp_mismatch e Packet.pp request Packet.pp reply)) open Cmdliner let serverip = let doc = "IP address of DNS server" in Arg.(required & pos 0 (some Dns_cli.ip_c) None & info [] ~doc ~docv:"SERVERIP") let port = let doc = "Port to connect to" in Arg.(value & opt int 53 & info [ "port" ] ~doc) let serial = let doc = "Serial number" in Arg.(value & opt int32 1l & info [ "serial" ] ~doc) let key = let doc = "DNS HMAC secret (name:alg:b64key)" in Arg.(value & opt (some Dns_cli.namekey_c) None & info [ "key" ] ~doc ~docv:"KEY") let zone = let doc = "Zone to notify" in Arg.(required & pos 1 (some Dns_cli.name_c) None & info [] ~doc ~docv:"ZONE") let cmd = let info = Cmd.info "onotify" ~version:"10.2.2" and term = Term.(term_result (const jump $ Dns_cli.setup_log $ serverip $ port $ zone $ key $ serial)) in Cmd.v info term let () = exit (Cmd.eval cmd)