Software /
code /
prosody
Comparison
util/prosodyctl/check.lua @ 13216:fcc052ca1652 0.12
util.prosodyctl.check: Get some config options via minimal moduleapi #896
The module API has certain coercion features that are useful.
Fixes traceback reported in #1812 and other duplicates
author | Kim Alvefur <zash@zash.se> |
---|---|
date | Mon, 17 Jul 2023 14:03:13 +0200 |
parent | 13121:332e95f75dbb |
child | 13217:b264ea91e930 |
comparison
equal
deleted
inserted
replaced
13178:e689d4c45681 | 13216:fcc052ca1652 |
---|---|
1 local configmanager = require "core.configmanager"; | 1 local configmanager = require "core.configmanager"; |
2 local moduleapi = require "core.moduleapi"; | |
2 local show_usage = require "util.prosodyctl".show_usage; | 3 local show_usage = require "util.prosodyctl".show_usage; |
3 local show_warning = require "util.prosodyctl".show_warning; | 4 local show_warning = require "util.prosodyctl".show_warning; |
4 local is_prosody_running = require "util.prosodyctl".isrunning; | 5 local is_prosody_running = require "util.prosodyctl".isrunning; |
5 local parse_args = require "util.argparse".parse; | 6 local parse_args = require "util.argparse".parse; |
6 local dependencies = require "util.dependencies"; | 7 local dependencies = require "util.dependencies"; |
9 local jid_split = require "util.jid".prepped_split; | 10 local jid_split = require "util.jid".prepped_split; |
10 local modulemanager = require "core.modulemanager"; | 11 local modulemanager = require "core.modulemanager"; |
11 local async = require "util.async"; | 12 local async = require "util.async"; |
12 local httputil = require "util.http"; | 13 local httputil = require "util.http"; |
13 | 14 |
15 local function api(host) | |
16 return setmetatable({ name = "prosodyctl.check"; host = host; log = prosody.log }, { __index = moduleapi }) | |
17 end | |
18 | |
14 local function check_ojn(check_type, target_host) | 19 local function check_ojn(check_type, target_host) |
15 local http = require "net.http"; -- .new({}); | 20 local http = require "net.http"; -- .new({}); |
16 local json = require "util.json"; | 21 local json = require "util.json"; |
17 | 22 |
18 local response, err = async.wait_for(http.request( | 23 local response, err = async.wait_for(http.request( |
315 show_warning("Note: The connectivity check will connect to a remote server."); | 320 show_warning("Note: The connectivity check will connect to a remote server."); |
316 return 1; | 321 return 1; |
317 end | 322 end |
318 if not what or what == "disabled" then | 323 if not what or what == "disabled" then |
319 local disabled_hosts_set = set.new(); | 324 local disabled_hosts_set = set.new(); |
320 for host, host_options in it.filter("*", pairs(configmanager.getconfig())) do | 325 for host in it.filter("*", pairs(configmanager.getconfig())) do |
321 if host_options.enabled == false then | 326 if api(host):get_option_boolean("enabled") == false then |
322 disabled_hosts_set:add(host); | 327 disabled_hosts_set:add(host); |
323 end | 328 end |
324 end | 329 end |
325 if not disabled_hosts_set:empty() then | 330 if not disabled_hosts_set:empty() then |
326 local msg = "Checks will be skipped for these disabled hosts: %s"; | 331 local msg = "Checks will be skipped for these disabled hosts: %s"; |
455 "websocket_frame_fragment_limit", | 460 "websocket_frame_fragment_limit", |
456 "websocket_get_response_body", | 461 "websocket_get_response_body", |
457 "websocket_get_response_text", | 462 "websocket_get_response_text", |
458 }); | 463 }); |
459 local config = configmanager.getconfig(); | 464 local config = configmanager.getconfig(); |
465 local global = api("*"); | |
460 -- Check that we have any global options (caused by putting a host at the top) | 466 -- Check that we have any global options (caused by putting a host at the top) |
461 if it.count(it.filter("log", pairs(config["*"]))) == 0 then | 467 if it.count(it.filter("log", pairs(config["*"]))) == 0 then |
462 ok = false; | 468 ok = false; |
463 print(""); | 469 print(""); |
464 print(" No global options defined. Perhaps you have put a host definition at the top") | 470 print(" No global options defined. Perhaps you have put a host definition at the top") |
489 end | 495 end |
490 print(); | 496 print(); |
491 end | 497 end |
492 | 498 |
493 do -- Check for modules enabled both normally and as components | 499 do -- Check for modules enabled both normally and as components |
494 local modules = set.new(config["*"]["modules_enabled"]); | 500 local modules = global:get_option_set("modules_enabled"); |
495 for host, options in enabled_hosts() do | 501 for host, options in enabled_hosts() do |
496 local component_module = options.component_module; | 502 local component_module = options.component_module; |
497 if component_module and modules:contains(component_module) then | 503 if component_module and modules:contains(component_module) then |
498 print((" mod_%s is enabled both in modules_enabled and as Component %q %q"):format(component_module, host, component_module)); | 504 print((" mod_%s is enabled both in modules_enabled and as Component %q %q"):format(component_module, host, component_module)); |
499 print(" This means the service is enabled on all VirtualHosts as well as the Component."); | 505 print(" This means the service is enabled on all VirtualHosts as well as the Component."); |
617 print(" fail."); | 623 print(" fail."); |
618 ok = false; | 624 ok = false; |
619 elseif all_options:contains("s2s_secure_domains") then | 625 elseif all_options:contains("s2s_secure_domains") then |
620 local secure_domains = set.new(); | 626 local secure_domains = set.new(); |
621 for host in enabled_hosts() do | 627 for host in enabled_hosts() do |
622 if config[host].s2s_secure_auth == true then | 628 if api(host):get_option_boolean("s2s_secure_auth") then |
623 secure_domains:add("*"); | 629 secure_domains:add("*"); |
624 else | 630 else |
625 secure_domains:include(set.new(config[host].s2s_secure_domains)); | 631 secure_domains:include(api(host):get_option_set("s2s_secure_domains", {})); |
626 end | 632 end |
627 end | 633 end |
628 if not secure_domains:empty() then | 634 if not secure_domains:empty() then |
629 print(""); | 635 print(""); |
630 print(" You have set s2s_secure_domains but your version of LuaSec does "); | 636 print(" You have set s2s_secure_domains but your version of LuaSec does "); |
639 print(" Connections will fail."); | 645 print(" Connections will fail."); |
640 ok = false; | 646 ok = false; |
641 end | 647 end |
642 | 648 |
643 do | 649 do |
644 local global_modules = set.new(config["*"].modules_enabled); | |
645 local registration_enabled_hosts = {}; | 650 local registration_enabled_hosts = {}; |
646 for host in enabled_hosts() do | 651 for host in enabled_hosts() do |
647 local host_modules = set.new(config[host].modules_enabled) + global_modules; | 652 local host_modules, component = modulemanager.get_modules_for_host(host); |
648 local allow_registration = config[host].allow_registration; | 653 local hostapi = api(host); |
654 local allow_registration = hostapi:get_option_boolean("allow_registration", false); | |
649 local mod_register = host_modules:contains("register"); | 655 local mod_register = host_modules:contains("register"); |
650 local mod_register_ibr = host_modules:contains("register_ibr"); | 656 local mod_register_ibr = host_modules:contains("register_ibr"); |
651 local mod_invites_register = host_modules:contains("invites_register"); | 657 local mod_invites_register = host_modules:contains("invites_register"); |
652 local registration_invite_only = config[host].registration_invite_only; | 658 local registration_invite_only = hostapi:get_option_boolean("registration_invite_only", true); |
653 local is_vhost = not config[host].component_module; | 659 local is_vhost = not component; |
654 if is_vhost and (mod_register_ibr or (mod_register and allow_registration)) | 660 if is_vhost and (mod_register_ibr or (mod_register and allow_registration)) |
655 and not (mod_invites_register and registration_invite_only) then | 661 and not (mod_invites_register and registration_invite_only) then |
656 table.insert(registration_enabled_hosts, host); | 662 table.insert(registration_enabled_hosts, host); |
657 end | 663 end |
658 end | 664 end |
670 | 676 |
671 do | 677 do |
672 local orphan_components = {}; | 678 local orphan_components = {}; |
673 local referenced_components = set.new(); | 679 local referenced_components = set.new(); |
674 local enabled_hosts_set = set.new(); | 680 local enabled_hosts_set = set.new(); |
675 for host, host_options in it.filter("*", pairs(configmanager.getconfig())) do | 681 for host in it.filter("*", pairs(configmanager.getconfig())) do |
676 if host_options.enabled ~= false then | 682 local hostapi = api(host); |
683 if hostapi:get_option_boolean("enabled", true) then | |
677 enabled_hosts_set:add(host); | 684 enabled_hosts_set:add(host); |
678 for _, disco_item in ipairs(host_options.disco_items or {}) do | 685 for _, disco_item in ipairs(hostapi:get_option_array("disco_items", {})) do |
679 referenced_components:add(disco_item[1]); | 686 referenced_components:add(disco_item[1]); |
680 end | 687 end |
681 end | 688 end |
682 end | 689 end |
683 for host, host_config in it.filter(skip_bare_jid_hosts, enabled_hosts()) do | 690 for host in it.filter(skip_bare_jid_hosts, enabled_hosts()) do |
684 local is_component = not not host_config.component_module; | 691 local is_component = not not select(2, modulemanager.get_modules_for_host(host)); |
685 if is_component then | 692 if is_component then |
686 local parent_domain = host:match("^[^.]+%.(.+)$"); | 693 local parent_domain = host:match("^[^.]+%.(.+)$"); |
687 local is_orphan = not (enabled_hosts_set:contains(parent_domain) or referenced_components:contains(host)); | 694 local is_orphan = not (enabled_hosts_set:contains(parent_domain) or referenced_components:contains(host)); |
688 if is_orphan then | 695 if is_orphan then |
689 table.insert(orphan_components, host); | 696 table.insert(orphan_components, host); |
711 local unbound = require"net.unbound"; | 718 local unbound = require"net.unbound"; |
712 dns = unbound.dns; | 719 dns = unbound.dns; |
713 end) | 720 end) |
714 local idna = require "util.encodings".idna; | 721 local idna = require "util.encodings".idna; |
715 local ip = require "util.ip"; | 722 local ip = require "util.ip"; |
716 local c2s_ports = set.new(configmanager.get("*", "c2s_ports") or {5222}); | 723 local global = api("*"); |
717 local s2s_ports = set.new(configmanager.get("*", "s2s_ports") or {5269}); | 724 local c2s_ports = global:get_option_set("c2s_ports", {5222}); |
718 local c2s_tls_ports = set.new(configmanager.get("*", "c2s_direct_tls_ports") or {}); | 725 local s2s_ports = global:get_option_set("s2s_ports", {5269}); |
719 local s2s_tls_ports = set.new(configmanager.get("*", "s2s_direct_tls_ports") or {}); | 726 local c2s_tls_ports = global:get_option_set("c2s_direct_tls_ports", {}); |
720 | 727 local s2s_tls_ports = global:get_option_set("s2s_direct_tls_ports", {}); |
721 if set.new(configmanager.get("*", "modules_enabled")):contains("net_multiplex") then | 728 |
722 local multiplex_ports = set.new(configmanager.get("*", "ports") or {}); | 729 local global_enabled = set.new(); |
723 local multiplex_tls_ports = set.new(configmanager.get("*", "ssl_ports") or {}); | 730 for host in enabled_hosts() do |
731 global_enabled:include(modulemanager.get_modules_for_host(host)); | |
732 end | |
733 if global_enabled:contains("net_multiplex") then | |
734 local multiplex_ports = global:get_option_set("ports", {}); | |
735 local multiplex_tls_ports = global:get_option_set("ssl_ports", {}); | |
724 if not multiplex_ports:empty() then | 736 if not multiplex_ports:empty() then |
725 c2s_ports = c2s_ports + multiplex_ports; | 737 c2s_ports = c2s_ports + multiplex_ports; |
726 s2s_ports = s2s_ports + multiplex_ports; | 738 s2s_ports = s2s_ports + multiplex_ports; |
727 end | 739 end |
728 if not multiplex_tls_ports:empty() then | 740 if not multiplex_tls_ports:empty() then |
779 internal_addresses:add(addr); | 791 internal_addresses:add(addr); |
780 end | 792 end |
781 end | 793 end |
782 | 794 |
783 -- Allow admin to specify additional (e.g. undiscoverable) IP addresses in the config | 795 -- Allow admin to specify additional (e.g. undiscoverable) IP addresses in the config |
784 for _, address in ipairs(configmanager.get("*", "external_addresses") or {}) do | 796 for _, address in ipairs(global:get_option_array("external_addresses", {})) do |
785 external_addresses:add(address); | 797 external_addresses:add(address); |
786 end | 798 end |
787 | 799 |
788 if external_addresses:empty() then | 800 if external_addresses:empty() then |
789 print(""); | 801 print(""); |
790 print(" Failed to determine the external addresses of this server. Checks may be inaccurate."); | 802 print(" Failed to determine the external addresses of this server. Checks may be inaccurate."); |
791 c2s_srv_required, s2s_srv_required = true, true; | 803 c2s_srv_required, s2s_srv_required = true, true; |
792 end | 804 end |
793 | 805 |
794 local v6_supported = not not socket.tcp6; | 806 local v6_supported = not not socket.tcp6; |
795 local use_ipv4 = configmanager.get("*", "use_ipv4") ~= false; | 807 local use_ipv4 = global:get_option_boolean("use_ipv4", true); |
796 local use_ipv6 = v6_supported and configmanager.get("*", "use_ipv6") ~= false; | 808 local use_ipv6 = global:get_option_boolean("use_ipv6", true); |
797 | 809 |
798 local function trim_dns_name(n) | 810 local function trim_dns_name(n) |
799 return (n:gsub("%.$", "")); | 811 return (n:gsub("%.$", "")); |
800 end | 812 end |
801 | 813 |
802 local unknown_addresses = set.new(); | 814 local unknown_addresses = set.new(); |
803 | 815 |
804 for jid, host_options in enabled_hosts() do | 816 for jid in enabled_hosts() do |
805 local all_targets_ok, some_targets_ok = true, false; | 817 local all_targets_ok, some_targets_ok = true, false; |
806 local node, host = jid_split(jid); | 818 local node, host = jid_split(jid); |
807 | 819 |
808 local modules, component_module = modulemanager.get_modules_for_host(host); | 820 local modules, component_module = modulemanager.get_modules_for_host(host); |
809 if component_module then | 821 if component_module then |
812 | 824 |
813 -- TODO Refactor these DNS SRV checks since they are very similar | 825 -- TODO Refactor these DNS SRV checks since they are very similar |
814 -- FIXME Suggest concrete actionable steps to correct issues so that | 826 -- FIXME Suggest concrete actionable steps to correct issues so that |
815 -- users don't have to copy-paste the message into the support chat and | 827 -- users don't have to copy-paste the message into the support chat and |
816 -- ask what to do about it. | 828 -- ask what to do about it. |
817 local is_component = not not host_options.component_module; | 829 local is_component = not not component_module; |
818 print("Checking DNS for "..(is_component and "component" or "host").." "..jid.."..."); | 830 print("Checking DNS for "..(is_component and "component" or "host").." "..jid.."..."); |
819 if node then | 831 if node then |
820 print("Only the domain part ("..host..") is used in DNS.") | 832 print("Only the domain part ("..host..") is used in DNS.") |
821 end | 833 end |
822 local target_hosts = set.new(); | 834 local target_hosts = set.new(); |
920 if use_ipv6 and not (AAAA and #AAAA > 0) then table.insert(prob, "AAAA"); end | 932 if use_ipv6 and not (AAAA and #AAAA > 0) then table.insert(prob, "AAAA"); end |
921 return prob; | 933 return prob; |
922 end | 934 end |
923 | 935 |
924 if modules:contains("proxy65") then | 936 if modules:contains("proxy65") then |
925 local proxy65_target = configmanager.get(host, "proxy65_address") or host; | 937 local proxy65_target = api(host):get_option_string("proxy65_address", host); |
926 if type(proxy65_target) == "string" then | 938 if type(proxy65_target) == "string" then |
927 local prob = check_address(proxy65_target); | 939 local prob = check_address(proxy65_target); |
928 if #prob > 0 then | 940 if #prob > 0 then |
929 print(" File transfer proxy "..proxy65_target.." has no "..table.concat(prob, "/") | 941 print(" File transfer proxy "..proxy65_target.." has no "..table.concat(prob, "/") |
930 .." record. Create one or set 'proxy65_address' to the correct host/IP."); | 942 .." record. Create one or set 'proxy65_address' to the correct host/IP."); |
940 end | 952 end |
941 | 953 |
942 if modules:contains("http") or not set.intersection(modules, known_http_modules):empty() | 954 if modules:contains("http") or not set.intersection(modules, known_http_modules):empty() |
943 or contains_match(modules, "^http_") or contains_match(modules, "_web$") then | 955 or contains_match(modules, "^http_") or contains_match(modules, "_web$") then |
944 | 956 |
945 local http_host = configmanager.get(host, "http_host") or host; | 957 local http_host = api(host):get_option_string("http_host", host); |
946 local http_internal_host = http_host; | 958 local http_internal_host = http_host; |
947 local http_url = configmanager.get(host, "http_external_url"); | 959 local http_url = api(host):get_option_string("http_external_url"); |
948 if http_url then | 960 if http_url then |
949 local url_parse = require "socket.url".parse; | 961 local url_parse = require "socket.url".parse; |
950 local external_url_parts = url_parse(http_url); | 962 local external_url_parts = url_parse(http_url); |
951 if external_url_parts then | 963 if external_url_parts then |
952 http_host = external_url_parts.host; | 964 http_host = external_url_parts.host; |
1126 elseif not cert:validat(os.time() + 86400*7) then | 1138 elseif not cert:validat(os.time() + 86400*7) then |
1127 print(" Certificate expires within one week.") | 1139 print(" Certificate expires within one week.") |
1128 elseif not cert:validat(os.time() + 86400*31) then | 1140 elseif not cert:validat(os.time() + 86400*31) then |
1129 print(" Certificate expires within one month.") | 1141 print(" Certificate expires within one month.") |
1130 end | 1142 end |
1131 if configmanager.get(host, "component_module") == nil | 1143 if select(2, modulemanager.get_modules_for_host(host)) == nil |
1132 and not x509_verify_identity(host, "_xmpp-client", cert) then | 1144 and not x509_verify_identity(host, "_xmpp-client", cert) then |
1133 print(" Not valid for client connections to "..host..".") | 1145 print(" Not valid for client connections to "..host..".") |
1134 cert_ok = false | 1146 cert_ok = false |
1135 end | 1147 end |
1136 if (not (configmanager.get(host, "anonymous_login") | 1148 if (not (api(host):get_option_boolean("anonymous_login", false) |
1137 or configmanager.get(host, "authentication") == "anonymous")) | 1149 or api(host):get_option_string("authentication", "internal_hashed") == "anonymous")) |
1138 and not x509_verify_identity(host, "_xmpp-server", cert) then | 1150 and not x509_verify_identity(host, "_xmpp-server", cert) then |
1139 print(" Not valid for server-to-server connections to "..host..".") | 1151 print(" Not valid for server-to-server connections to "..host..".") |
1140 cert_ok = false | 1152 cert_ok = false |
1141 end | 1153 end |
1142 end | 1154 end |
1151 print("") | 1163 print("") |
1152 end | 1164 end |
1153 -- intentionally not doing this by default | 1165 -- intentionally not doing this by default |
1154 if what == "connectivity" then | 1166 if what == "connectivity" then |
1155 local _, prosody_is_running = is_prosody_running(); | 1167 local _, prosody_is_running = is_prosody_running(); |
1156 if configmanager.get("*", "pidfile") and not prosody_is_running then | 1168 if api("*"):get_option_string("pidfile") and not prosody_is_running then |
1157 print("Prosody does not appear to be running, which is required for this test."); | 1169 print("Prosody does not appear to be running, which is required for this test."); |
1158 print("Start it and then try again."); | 1170 print("Start it and then try again."); |
1159 return 1; | 1171 return 1; |
1160 end | 1172 end |
1161 | 1173 |
1165 ["xmpp-client"] = "c2s_normal_auth"; | 1177 ["xmpp-client"] = "c2s_normal_auth"; |
1166 ["xmpp-server"] = "s2s_normal"; | 1178 ["xmpp-server"] = "s2s_normal"; |
1167 ["xmpps-client"] = nil; -- TODO | 1179 ["xmpps-client"] = nil; -- TODO |
1168 ["xmpps-server"] = nil; -- TODO | 1180 ["xmpps-server"] = nil; -- TODO |
1169 }; | 1181 }; |
1170 local probe_settings = configmanager.get("*", "connectivity_probe"); | 1182 local probe_settings = api("*"):get_option_string("connectivity_probe"); |
1171 if type(probe_settings) == "string" then | 1183 if type(probe_settings) == "string" then |
1172 probe_instance = probe_settings; | 1184 probe_instance = probe_settings; |
1173 elseif type(probe_settings) == "table" and type(probe_settings.url) == "string" then | 1185 elseif type(probe_settings) == "table" and type(probe_settings.url) == "string" then |
1174 probe_instance = probe_settings.url; | 1186 probe_instance = probe_settings.url; |
1175 if type(probe_settings.modules) == "table" then | 1187 if type(probe_settings.modules) == "table" then |
1223 end | 1235 end |
1224 end | 1236 end |
1225 | 1237 |
1226 if modules:contains("c2s") then | 1238 if modules:contains("c2s") then |
1227 check_connectivity("xmpp-client") | 1239 check_connectivity("xmpp-client") |
1228 if configmanager.get("*", "c2s_direct_tls_ports") then | 1240 if not api("*"):get_option_set("c2s_direct_tls_ports", {}):empty() then |
1229 check_connectivity("xmpps-client"); | 1241 check_connectivity("xmpps-client"); |
1230 end | 1242 end |
1231 end | 1243 end |
1232 | 1244 |
1233 if modules:contains("s2s") then | 1245 if modules:contains("s2s") then |
1234 check_connectivity("xmpp-server") | 1246 check_connectivity("xmpp-server") |
1235 if configmanager.get("*", "s2s_direct_tls_ports") then | 1247 if not api("*"):get_option_set("s2s_direct_tls_ports", {}):empty() then |
1236 check_connectivity("xmpps-server"); | 1248 check_connectivity("xmpps-server"); |
1237 end | 1249 end |
1238 end | 1250 end |
1239 | 1251 |
1240 print() | 1252 print() |
1248 local turn_services = {}; | 1260 local turn_services = {}; |
1249 | 1261 |
1250 for host in enabled_hosts() do | 1262 for host in enabled_hosts() do |
1251 local has_external_turn = modulemanager.get_modules_for_host(host):contains("turn_external"); | 1263 local has_external_turn = modulemanager.get_modules_for_host(host):contains("turn_external"); |
1252 if has_external_turn then | 1264 if has_external_turn then |
1265 local hostapi = api(host); | |
1253 table.insert(turn_enabled_hosts, host); | 1266 table.insert(turn_enabled_hosts, host); |
1254 local turn_host = configmanager.get(host, "turn_external_host") or host; | 1267 local turn_host = hostapi:get_option_string("turn_external_host", host); |
1255 local turn_port = configmanager.get(host, "turn_external_port") or 3478; | 1268 local turn_port = hostapi:get_option_number("turn_external_port", 3478); |
1256 local turn_secret = configmanager.get(host, "turn_external_secret"); | 1269 local turn_secret = hostapi:get_option_string("turn_external_secret"); |
1257 if not turn_secret then | 1270 if not turn_secret then |
1258 print("Error: Your configuration is missing a turn_external_secret for "..host); | 1271 print("Error: Your configuration is missing a turn_external_secret for "..host); |
1259 print("Error: TURN will not be advertised for this host."); | 1272 print("Error: TURN will not be advertised for this host."); |
1260 ok = false; | 1273 ok = false; |
1261 else | 1274 else |