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