Software /
code /
prosody-modules
Comparison
mod_dnsupdate/mod_dnsupdate.lua @ 4762:ba312cd7907f
mod_dnsupdate: Use with nsupdate to update DNS records from config
author | Kim Alvefur <zash@zash.se> |
---|---|
date | Sat, 06 Nov 2021 14:48:35 +0100 |
child | 4763:91077c928c57 |
comparison
equal
deleted
inserted
replaced
4761:ea1ecbc1d04d | 4762:ba312cd7907f |
---|---|
1 module:set_global(); | |
2 | |
3 local config = require "core.configmanager"; | |
4 local argparse = require "util.argparse"; | |
5 local dns = require"net.adns".resolver(); | |
6 local async = require "util.async"; | |
7 local set = require "util.set"; | |
8 local nameprep = require"util.encodings".stringprep.nameprep; | |
9 local idna_to_ascii = require"util.encodings".idna.to_ascii; | |
10 | |
11 local services = { "xmpp-client"; "xmpps-client"; "xmpp-server"; "xmpps-server" } | |
12 | |
13 local function validate_dnsname_option(options, option_name, default) | |
14 local host = options[option_name]; | |
15 if host == nil then return default end | |
16 local normalized = nameprep(host); | |
17 if not normalized then | |
18 module:log("error", "--%s %q fails normalization"); | |
19 return; | |
20 end | |
21 local alabel = idna_to_ascii(normalized); | |
22 if not alabel then | |
23 module:log("error", "--%s %q fails IDNA"); | |
24 return; | |
25 end | |
26 return alabel; | |
27 end | |
28 | |
29 function module.command(arg) | |
30 local opts = argparse.parse(arg, { | |
31 short_params = { d = "domain"; p = "primary"; t = "target"; l = "ttl"; h = "help"; ["?"] = "help" }; | |
32 value_params = { domain = true; primary = true; target = true; ttl = true }; | |
33 }); | |
34 | |
35 if not arg[1] or arg[2] or not opts or opts.help or not opts.domain then | |
36 local out = opts.help and io.stdout or io.stderr; | |
37 out:write("prosodyctl mod_dnsupdate [options] virtualhost\n"); | |
38 out:write("\t-d --domain\tbase domain name *required*\n"); | |
39 out:write("\t-p --primary\tprimary DNS name server\n"); | |
40 out:write("\t-t --target\ttarget hostname for SRV\n"); | |
41 out:write("\t-l --ttl\tTTL to use\n"); | |
42 out:write("\t--each\tremove and replace individual SRV records\n"); | |
43 out:write("\t--reset\tremove and replace all SRV records\n"); | |
44 return opts and opts.help and 0 or 1; | |
45 end | |
46 | |
47 local vhost = nameprep(arg[1]); -- TODO loop over arg[]? | |
48 if not vhost then | |
49 module:log("error", "Host %q fails normalization", arg[1]); | |
50 return 1; | |
51 end | |
52 local ihost = idna_to_ascii(vhost); | |
53 if not ihost then | |
54 module:log("error", "Host %q falis IDNA", vhost); | |
55 return 1; | |
56 end | |
57 if not config.get(vhost, "defined") then | |
58 module:log("error", "Host %q is not defined in the config", vhost); | |
59 return 1; | |
60 end | |
61 | |
62 local domain = validate_dnsname_option(opts, "domain"); | |
63 if not domain then | |
64 module:log("error", "--domain is required"); | |
65 return 1; | |
66 end | |
67 local primary = validate_dnsname_option(opts, "primary") | |
68 or async.wait_for(dns:lookup_promise(domain, "SOA"):next(function(ret) return ret[1].soa.mname; end)); | |
69 if not primary then | |
70 module:log("error", "Could not discover primary name server, specify it with --primary"); | |
71 return 1; | |
72 end | |
73 local target = validate_dnsname_option(opts, "target", module:context(vhost):get_option_string("xmpp_host", ihost)); | |
74 -- TODO validate that target has A/AAAA | |
75 | |
76 local configured_ports = { | |
77 ["xmpp-client"] = module:get_option_array("c2s_ports", { 5222 }); | |
78 ["xmpp-server"] = module:get_option_array("c2s_ports", { 5269 }); | |
79 ["xmpps-client"] = module:get_option_array("c2s_direct_tls_ports", {}); | |
80 ["xmpps-server"] = module:get_option_array("c2s_ports", {}); | |
81 }; | |
82 | |
83 if opts.multiplex then | |
84 for opt, ports in pairs(configured_ports) do | |
85 ports:append(module:get_option_array(opt:sub(1, 5) == "xmpps" and "ssl_ports" or "ports", {})); | |
86 end | |
87 end | |
88 | |
89 local existing_srv = {}; | |
90 for _, service in ipairs(services) do | |
91 existing_srv[service] = dns:lookup_promise(("_%s._tcp.%s"):format(service, ihost), "SRV"); | |
92 end | |
93 | |
94 print("zone", domain); | |
95 print("server", primary); | |
96 print("ttl " .. tostring(opts.ttl or 60 * 60)); | |
97 | |
98 for _, service in ipairs(services) do | |
99 local ports = set.new(configured_ports[service]); | |
100 local records = (async.wait_for(existing_srv[service])); | |
101 local replace = opts.reset; | |
102 for _, rr in ipairs(records) do | |
103 if not ports:contains(rr.srv.port) or target ~= nameprep(rr.srv.target):gsub("%.$", "") then | |
104 if not opts.each then | |
105 replace = true; | |
106 break | |
107 end | |
108 print(("del _%s._tcp.%s IN SRV %s"):format(service, ihost, rr)); | |
109 end | |
110 end | |
111 if replace then | |
112 print(("del _%s._tcp.%s IN SRV"):format(service, ihost)); | |
113 for port in ports do print(("add _%s._tcp.%s IN SRV 1 1 %d %s"):format(service, ihost, port, target)); end | |
114 end | |
115 end | |
116 | |
117 print("show"); | |
118 print("send"); | |
119 print("answer"); | |
120 end |