Mercurial > 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 |
| parents | |
| children | 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 |
