Comparison

util/prosodyctl/check.lua @ 13218:e576c6a0d1f8

Merge 0.12->trunk
author Kim Alvefur <zash@zash.se>
date Mon, 17 Jul 2023 14:47:39 +0200
parent 13122:45458ecaacae
parent 13217:b264ea91e930
child 13220:56decf85db1d
comparison
equal deleted inserted replaced
13215:b1c2e70de470 13218:e576c6a0d1f8
1 local configmanager = require "prosody.core.configmanager"; 1 local configmanager = require "prosody.core.configmanager";
2 local moduleapi = require "prosody.core.moduleapi";
2 local show_usage = require "prosody.util.prosodyctl".show_usage; 3 local show_usage = require "prosody.util.prosodyctl".show_usage;
3 local show_warning = require "prosody.util.prosodyctl".show_warning; 4 local show_warning = require "prosody.util.prosodyctl".show_warning;
4 local is_prosody_running = require "prosody.util.prosodyctl".isrunning; 5 local is_prosody_running = require "prosody.util.prosodyctl".isrunning;
5 local parse_args = require "prosody.util.argparse".parse; 6 local parse_args = require "prosody.util.argparse".parse;
6 local dependencies = require "prosody.util.dependencies"; 7 local dependencies = require "prosody.util.dependencies";
9 local jid_split = require "prosody.util.jid".prepped_split; 10 local jid_split = require "prosody.util.jid".prepped_split;
10 local modulemanager = require "prosody.core.modulemanager"; 11 local modulemanager = require "prosody.core.modulemanager";
11 local async = require "prosody.util.async"; 12 local async = require "prosody.util.async";
12 local httputil = require "prosody.util.http"; 13 local httputil = require "prosody.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 "prosody.net.http"; -- .new({}); 20 local http = require "prosody.net.http"; -- .new({});
16 local json = require "prosody.util.json"; 21 local json = require "prosody.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")
488 print(" "..tostring(suggested_global_modules / function (x) return ("%q"):format(x) end)); 494 print(" "..tostring(suggested_global_modules / function (x) return ("%q"):format(x) end));
489 end 495 end
490 print(); 496 print();
491 end 497 end
492 498
499 local function validate_module_list(host, name, modules)
500 if modules == nil then
501 return -- okay except for global section, checked separately
502 end
503 local t = type(modules)
504 if t ~= "table" then
505 print(" The " .. name .. " in the " .. host .. " section should not be a " .. t .. " but a list of strings, e.g.");
506 print(" " .. name .. " = { \"name_of_module\", \"another_plugin\", }")
507 print()
508 ok = false
509 return
510 end
511 for k, v in pairs(modules) do
512 if type(k) ~= "number" or type(v) ~= "string" then
513 print(" The " .. name .. " in the " .. host .. " section should not be a map of " .. type(k) .. " to " .. type(v)
514 .. " but a list of strings, e.g.");
515 print(" " .. name .. " = { \"name_of_module\", \"another_plugin\", }")
516 ok = false
517 break
518 end
519 end
520 end
521
522 for host, options in enabled_hosts() do
523 validate_module_list(host, "modules_enabled", options.modules_enabled);
524 validate_module_list(host, "modules_disabled", options.modules_disabled);
525 end
526
493 do -- Check for modules enabled both normally and as components 527 do -- Check for modules enabled both normally and as components
494 local modules = set.new(config["*"]["modules_enabled"]); 528 local modules = global:get_option_set("modules_enabled");
495 for host, options in enabled_hosts() do 529 for host, options in enabled_hosts() do
496 local component_module = options.component_module; 530 local component_module = options.component_module;
497 if component_module and modules:contains(component_module) then 531 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)); 532 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."); 533 print(" This means the service is enabled on all VirtualHosts as well as the Component.");
617 print(" fail."); 651 print(" fail.");
618 ok = false; 652 ok = false;
619 elseif all_options:contains("s2s_secure_domains") then 653 elseif all_options:contains("s2s_secure_domains") then
620 local secure_domains = set.new(); 654 local secure_domains = set.new();
621 for host in enabled_hosts() do 655 for host in enabled_hosts() do
622 if config[host].s2s_secure_auth == true then 656 if api(host):get_option_boolean("s2s_secure_auth") then
623 secure_domains:add("*"); 657 secure_domains:add("*");
624 else 658 else
625 secure_domains:include(set.new(config[host].s2s_secure_domains)); 659 secure_domains:include(api(host):get_option_set("s2s_secure_domains", {}));
626 end 660 end
627 end 661 end
628 if not secure_domains:empty() then 662 if not secure_domains:empty() then
629 print(""); 663 print("");
630 print(" You have set s2s_secure_domains but your version of LuaSec does "); 664 print(" You have set s2s_secure_domains but your version of LuaSec does ");
639 print(" Connections will fail."); 673 print(" Connections will fail.");
640 ok = false; 674 ok = false;
641 end 675 end
642 676
643 do 677 do
644 local global_modules = set.new(config["*"].modules_enabled);
645 local registration_enabled_hosts = {}; 678 local registration_enabled_hosts = {};
646 for host in enabled_hosts() do 679 for host in enabled_hosts() do
647 local host_modules = set.new(config[host].modules_enabled) + global_modules; 680 local host_modules, component = modulemanager.get_modules_for_host(host);
648 local allow_registration = config[host].allow_registration; 681 local hostapi = api(host);
682 local allow_registration = hostapi:get_option_boolean("allow_registration", false);
649 local mod_register = host_modules:contains("register"); 683 local mod_register = host_modules:contains("register");
650 local mod_register_ibr = host_modules:contains("register_ibr"); 684 local mod_register_ibr = host_modules:contains("register_ibr");
651 local mod_invites_register = host_modules:contains("invites_register"); 685 local mod_invites_register = host_modules:contains("invites_register");
652 local registration_invite_only = config[host].registration_invite_only; 686 local registration_invite_only = hostapi:get_option_boolean("registration_invite_only", true);
653 local is_vhost = not config[host].component_module; 687 local is_vhost = not component;
654 if is_vhost and (mod_register_ibr or (mod_register and allow_registration)) 688 if is_vhost and (mod_register_ibr or (mod_register and allow_registration))
655 and not (mod_invites_register and registration_invite_only) then 689 and not (mod_invites_register and registration_invite_only) then
656 table.insert(registration_enabled_hosts, host); 690 table.insert(registration_enabled_hosts, host);
657 end 691 end
658 end 692 end
670 704
671 do 705 do
672 local orphan_components = {}; 706 local orphan_components = {};
673 local referenced_components = set.new(); 707 local referenced_components = set.new();
674 local enabled_hosts_set = set.new(); 708 local enabled_hosts_set = set.new();
675 for host, host_options in it.filter("*", pairs(configmanager.getconfig())) do 709 for host in it.filter("*", pairs(configmanager.getconfig())) do
676 if host_options.enabled ~= false then 710 local hostapi = api(host);
711 if hostapi:get_option_boolean("enabled", true) then
677 enabled_hosts_set:add(host); 712 enabled_hosts_set:add(host);
678 for _, disco_item in ipairs(host_options.disco_items or {}) do 713 for _, disco_item in ipairs(hostapi:get_option_array("disco_items", {})) do
679 referenced_components:add(disco_item[1]); 714 referenced_components:add(disco_item[1]);
680 end 715 end
681 end 716 end
682 end 717 end
683 for host, host_config in it.filter(skip_bare_jid_hosts, enabled_hosts()) do 718 for host in it.filter(skip_bare_jid_hosts, enabled_hosts()) do
684 local is_component = not not host_config.component_module; 719 local is_component = not not select(2, modulemanager.get_modules_for_host(host));
685 if is_component then 720 if is_component then
686 local parent_domain = host:match("^[^.]+%.(.+)$"); 721 local parent_domain = host:match("^[^.]+%.(.+)$");
687 local is_orphan = not (enabled_hosts_set:contains(parent_domain) or referenced_components:contains(host)); 722 local is_orphan = not (enabled_hosts_set:contains(parent_domain) or referenced_components:contains(host));
688 if is_orphan then 723 if is_orphan then
689 table.insert(orphan_components, host); 724 table.insert(orphan_components, host);
711 local unbound = require"prosody.net.unbound"; 746 local unbound = require"prosody.net.unbound";
712 dns = unbound.dns; 747 dns = unbound.dns;
713 end) 748 end)
714 local idna = require "prosody.util.encodings".idna; 749 local idna = require "prosody.util.encodings".idna;
715 local ip = require "prosody.util.ip"; 750 local ip = require "prosody.util.ip";
716 local c2s_ports = set.new(configmanager.get("*", "c2s_ports") or {5222}); 751 local global = api("*");
717 local s2s_ports = set.new(configmanager.get("*", "s2s_ports") or {5269}); 752 local c2s_ports = global:get_option_set("c2s_ports", {5222});
718 local c2s_tls_ports = set.new(configmanager.get("*", "c2s_direct_tls_ports") or {}); 753 local s2s_ports = global:get_option_set("s2s_ports", {5269});
719 local s2s_tls_ports = set.new(configmanager.get("*", "s2s_direct_tls_ports") or {}); 754 local c2s_tls_ports = global:get_option_set("c2s_direct_tls_ports", {});
720 755 local s2s_tls_ports = global:get_option_set("s2s_direct_tls_ports", {});
721 if set.new(configmanager.get("*", "modules_enabled")):contains("net_multiplex") then 756
722 local multiplex_ports = set.new(configmanager.get("*", "ports") or {}); 757 local global_enabled = set.new();
723 local multiplex_tls_ports = set.new(configmanager.get("*", "ssl_ports") or {}); 758 for host in enabled_hosts() do
759 global_enabled:include(modulemanager.get_modules_for_host(host));
760 end
761 if global_enabled:contains("net_multiplex") then
762 local multiplex_ports = global:get_option_set("ports", {});
763 local multiplex_tls_ports = global:get_option_set("ssl_ports", {});
724 if not multiplex_ports:empty() then 764 if not multiplex_ports:empty() then
725 c2s_ports = c2s_ports + multiplex_ports; 765 c2s_ports = c2s_ports + multiplex_ports;
726 s2s_ports = s2s_ports + multiplex_ports; 766 s2s_ports = s2s_ports + multiplex_ports;
727 end 767 end
728 if not multiplex_tls_ports:empty() then 768 if not multiplex_tls_ports:empty() then
779 internal_addresses:add(addr); 819 internal_addresses:add(addr);
780 end 820 end
781 end 821 end
782 822
783 -- Allow admin to specify additional (e.g. undiscoverable) IP addresses in the config 823 -- Allow admin to specify additional (e.g. undiscoverable) IP addresses in the config
784 for _, address in ipairs(configmanager.get("*", "external_addresses") or {}) do 824 for _, address in ipairs(global:get_option_array("external_addresses", {})) do
785 external_addresses:add(address); 825 external_addresses:add(address);
786 end 826 end
787 827
788 if external_addresses:empty() then 828 if external_addresses:empty() then
789 print(""); 829 print("");
790 print(" Failed to determine the external addresses of this server. Checks may be inaccurate."); 830 print(" Failed to determine the external addresses of this server. Checks may be inaccurate.");
791 c2s_srv_required, s2s_srv_required = true, true; 831 c2s_srv_required, s2s_srv_required = true, true;
792 end 832 end
793 833
794 local v6_supported = not not socket.tcp6; 834 local v6_supported = not not socket.tcp6;
795 local use_ipv4 = configmanager.get("*", "use_ipv4") ~= false; 835 local use_ipv4 = global:get_option_boolean("use_ipv4", true);
796 local use_ipv6 = v6_supported and configmanager.get("*", "use_ipv6") ~= false; 836 local use_ipv6 = global:get_option_boolean("use_ipv6", true);
797 837
798 local function trim_dns_name(n) 838 local function trim_dns_name(n)
799 return (n:gsub("%.$", "")); 839 return (n:gsub("%.$", ""));
800 end 840 end
801 841
802 local unknown_addresses = set.new(); 842 local unknown_addresses = set.new();
803 843
804 for jid, host_options in enabled_hosts() do 844 for jid in enabled_hosts() do
805 local all_targets_ok, some_targets_ok = true, false; 845 local all_targets_ok, some_targets_ok = true, false;
806 local node, host = jid_split(jid); 846 local node, host = jid_split(jid);
807 847
808 local modules, component_module = modulemanager.get_modules_for_host(host); 848 local modules, component_module = modulemanager.get_modules_for_host(host);
809 if component_module then 849 if component_module then
812 852
813 -- TODO Refactor these DNS SRV checks since they are very similar 853 -- TODO Refactor these DNS SRV checks since they are very similar
814 -- FIXME Suggest concrete actionable steps to correct issues so that 854 -- 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 855 -- users don't have to copy-paste the message into the support chat and
816 -- ask what to do about it. 856 -- ask what to do about it.
817 local is_component = not not host_options.component_module; 857 local is_component = not not component_module;
818 print("Checking DNS for "..(is_component and "component" or "host").." "..jid.."..."); 858 print("Checking DNS for "..(is_component and "component" or "host").." "..jid.."...");
819 if node then 859 if node then
820 print("Only the domain part ("..host..") is used in DNS.") 860 print("Only the domain part ("..host..") is used in DNS.")
821 end 861 end
822 local target_hosts = set.new(); 862 local target_hosts = set.new();
920 if use_ipv6 and not (AAAA and #AAAA > 0) then table.insert(prob, "AAAA"); end 960 if use_ipv6 and not (AAAA and #AAAA > 0) then table.insert(prob, "AAAA"); end
921 return prob; 961 return prob;
922 end 962 end
923 963
924 if modules:contains("proxy65") then 964 if modules:contains("proxy65") then
925 local proxy65_target = configmanager.get(host, "proxy65_address") or host; 965 local proxy65_target = api(host):get_option_string("proxy65_address", host);
926 if type(proxy65_target) == "string" then 966 if type(proxy65_target) == "string" then
927 local prob = check_address(proxy65_target); 967 local prob = check_address(proxy65_target);
928 if #prob > 0 then 968 if #prob > 0 then
929 print(" File transfer proxy "..proxy65_target.." has no "..table.concat(prob, "/") 969 print(" File transfer proxy "..proxy65_target.." has no "..table.concat(prob, "/")
930 .." record. Create one or set 'proxy65_address' to the correct host/IP."); 970 .." record. Create one or set 'proxy65_address' to the correct host/IP.");
940 end 980 end
941 981
942 if modules:contains("http") or not set.intersection(modules, known_http_modules):empty() 982 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 983 or contains_match(modules, "^http_") or contains_match(modules, "_web$") then
944 984
945 local http_host = configmanager.get(host, "http_host") or host; 985 local http_host = api(host):get_option_string("http_host", host);
946 local http_internal_host = http_host; 986 local http_internal_host = http_host;
947 local http_url = configmanager.get(host, "http_external_url"); 987 local http_url = api(host):get_option_string("http_external_url");
948 if http_url then 988 if http_url then
949 local url_parse = require "socket.url".parse; 989 local url_parse = require "socket.url".parse;
950 local external_url_parts = url_parse(http_url); 990 local external_url_parts = url_parse(http_url);
951 if external_url_parts then 991 if external_url_parts then
952 http_host = external_url_parts.host; 992 http_host = external_url_parts.host;
1126 elseif not cert:validat(os.time() + 86400*7) then 1166 elseif not cert:validat(os.time() + 86400*7) then
1127 print(" Certificate expires within one week.") 1167 print(" Certificate expires within one week.")
1128 elseif not cert:validat(os.time() + 86400*31) then 1168 elseif not cert:validat(os.time() + 86400*31) then
1129 print(" Certificate expires within one month.") 1169 print(" Certificate expires within one month.")
1130 end 1170 end
1131 if configmanager.get(host, "component_module") == nil 1171 if select(2, modulemanager.get_modules_for_host(host)) == nil
1132 and not x509_verify_identity(host, "_xmpp-client", cert) then 1172 and not x509_verify_identity(host, "_xmpp-client", cert) then
1133 print(" Not valid for client connections to "..host..".") 1173 print(" Not valid for client connections to "..host..".")
1134 cert_ok = false 1174 cert_ok = false
1135 end 1175 end
1136 if (not (configmanager.get(host, "anonymous_login") 1176 if (not (api(host):get_option_boolean("anonymous_login", false)
1137 or configmanager.get(host, "authentication") == "anonymous")) 1177 or api(host):get_option_string("authentication", "internal_hashed") == "anonymous"))
1138 and not x509_verify_identity(host, "_xmpp-server", cert) then 1178 and not x509_verify_identity(host, "_xmpp-server", cert) then
1139 print(" Not valid for server-to-server connections to "..host..".") 1179 print(" Not valid for server-to-server connections to "..host..".")
1140 cert_ok = false 1180 cert_ok = false
1141 end 1181 end
1142 end 1182 end
1151 print("") 1191 print("")
1152 end 1192 end
1153 -- intentionally not doing this by default 1193 -- intentionally not doing this by default
1154 if what == "connectivity" then 1194 if what == "connectivity" then
1155 local _, prosody_is_running = is_prosody_running(); 1195 local _, prosody_is_running = is_prosody_running();
1156 if configmanager.get("*", "pidfile") and not prosody_is_running then 1196 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."); 1197 print("Prosody does not appear to be running, which is required for this test.");
1158 print("Start it and then try again."); 1198 print("Start it and then try again.");
1159 return 1; 1199 return 1;
1160 end 1200 end
1161 1201
1165 ["xmpp-client"] = "c2s_normal_auth"; 1205 ["xmpp-client"] = "c2s_normal_auth";
1166 ["xmpp-server"] = "s2s_normal"; 1206 ["xmpp-server"] = "s2s_normal";
1167 ["xmpps-client"] = nil; -- TODO 1207 ["xmpps-client"] = nil; -- TODO
1168 ["xmpps-server"] = nil; -- TODO 1208 ["xmpps-server"] = nil; -- TODO
1169 }; 1209 };
1170 local probe_settings = configmanager.get("*", "connectivity_probe"); 1210 local probe_settings = api("*"):get_option_string("connectivity_probe");
1171 if type(probe_settings) == "string" then 1211 if type(probe_settings) == "string" then
1172 probe_instance = probe_settings; 1212 probe_instance = probe_settings;
1173 elseif type(probe_settings) == "table" and type(probe_settings.url) == "string" then 1213 elseif type(probe_settings) == "table" and type(probe_settings.url) == "string" then
1174 probe_instance = probe_settings.url; 1214 probe_instance = probe_settings.url;
1175 if type(probe_settings.modules) == "table" then 1215 if type(probe_settings.modules) == "table" then
1223 end 1263 end
1224 end 1264 end
1225 1265
1226 if modules:contains("c2s") then 1266 if modules:contains("c2s") then
1227 check_connectivity("xmpp-client") 1267 check_connectivity("xmpp-client")
1228 if configmanager.get("*", "c2s_direct_tls_ports") then 1268 if not api("*"):get_option_set("c2s_direct_tls_ports", {}):empty() then
1229 check_connectivity("xmpps-client"); 1269 check_connectivity("xmpps-client");
1230 end 1270 end
1231 end 1271 end
1232 1272
1233 if modules:contains("s2s") then 1273 if modules:contains("s2s") then
1234 check_connectivity("xmpp-server") 1274 check_connectivity("xmpp-server")
1235 if configmanager.get("*", "s2s_direct_tls_ports") then 1275 if not api("*"):get_option_set("s2s_direct_tls_ports", {}):empty() then
1236 check_connectivity("xmpps-server"); 1276 check_connectivity("xmpps-server");
1237 end 1277 end
1238 end 1278 end
1239 1279
1240 print() 1280 print()
1248 local turn_services = {}; 1288 local turn_services = {};
1249 1289
1250 for host in enabled_hosts() do 1290 for host in enabled_hosts() do
1251 local has_external_turn = modulemanager.get_modules_for_host(host):contains("turn_external"); 1291 local has_external_turn = modulemanager.get_modules_for_host(host):contains("turn_external");
1252 if has_external_turn then 1292 if has_external_turn then
1293 local hostapi = api(host);
1253 table.insert(turn_enabled_hosts, host); 1294 table.insert(turn_enabled_hosts, host);
1254 local turn_host = configmanager.get(host, "turn_external_host") or host; 1295 local turn_host = hostapi:get_option_string("turn_external_host", host);
1255 local turn_port = configmanager.get(host, "turn_external_port") or 3478; 1296 local turn_port = hostapi:get_option_number("turn_external_port", 3478);
1256 local turn_secret = configmanager.get(host, "turn_external_secret"); 1297 local turn_secret = hostapi:get_option_string("turn_external_secret");
1257 if not turn_secret then 1298 if not turn_secret then
1258 print("Error: Your configuration is missing a turn_external_secret for "..host); 1299 print("Error: Your configuration is missing a turn_external_secret for "..host);
1259 print("Error: TURN will not be advertised for this host."); 1300 print("Error: TURN will not be advertised for this host.");
1260 ok = false; 1301 ok = false;
1261 else 1302 else