Software /
code /
prosody
Comparison
prosodyctl @ 10871:e5dee71d0ebb
prosodyctl+util.prosodyctl.*: Start breaking up the ever-growing prosodyctl
author | Matthew Wild <mwild1@gmail.com> |
---|---|
date | Tue, 02 Jun 2020 08:01:21 +0100 |
parent | 10858:efa49d484560 |
child | 10905:709255e332d8 |
comparison
equal
deleted
inserted
replaced
10870:3f1889608f3e | 10871:e5dee71d0ebb |
---|---|
47 local startup = require "util.startup"; | 47 local startup = require "util.startup"; |
48 startup.prosodyctl(); | 48 startup.prosodyctl(); |
49 | 49 |
50 ----------- | 50 ----------- |
51 | 51 |
52 local error_messages = setmetatable({ | |
53 ["invalid-username"] = "The given username is invalid in a Jabber ID"; | |
54 ["invalid-hostname"] = "The given hostname is invalid"; | |
55 ["no-password"] = "No password was supplied"; | |
56 ["no-such-user"] = "The given user does not exist on the server"; | |
57 ["no-such-host"] = "The given hostname does not exist in the config"; | |
58 ["unable-to-save-data"] = "Unable to store, perhaps you don't have permission?"; | |
59 ["no-pidfile"] = "There is no 'pidfile' option in the configuration file, see https://prosody.im/doc/prosodyctl#pidfile for help"; | |
60 ["invalid-pidfile"] = "The 'pidfile' option in the configuration file is not a string, see https://prosody.im/doc/prosodyctl#pidfile for help"; | |
61 ["no-posix"] = "The mod_posix module is not enabled in the Prosody config file, see https://prosody.im/doc/prosodyctl for more info"; | |
62 ["no-such-method"] = "This module has no commands"; | |
63 ["not-running"] = "Prosody is not running"; | |
64 }, { __index = function (_,k) return "Error: "..(tostring(k):gsub("%-", " "):gsub("^.", string.upper)); end }); | |
65 | |
66 local configmanager = require "core.configmanager"; | 52 local configmanager = require "core.configmanager"; |
67 local modulemanager = require "core.modulemanager" | 53 local modulemanager = require "core.modulemanager" |
68 local prosodyctl = require "util.prosodyctl" | 54 local prosodyctl = require "util.prosodyctl" |
69 local socket = require "socket" | 55 local socket = require "socket" |
70 local dependencies = require "util.dependencies"; | 56 local dependencies = require "util.dependencies"; |
71 local lfs = dependencies.softreq "lfs"; | 57 local lfs = dependencies.softreq "lfs"; |
72 | 58 |
73 ----------------------- | 59 ----------------------- |
74 | 60 |
61 local human_io = require "util.human.io"; | |
62 | |
75 local show_message, show_warning = prosodyctl.show_message, prosodyctl.show_warning; | 63 local show_message, show_warning = prosodyctl.show_message, prosodyctl.show_warning; |
76 local show_usage = prosodyctl.show_usage; | 64 local show_usage = prosodyctl.show_usage; |
77 local show_yesno = prosodyctl.show_yesno; | 65 local read_password = human_io.read_password; |
78 local show_prompt = prosodyctl.show_prompt; | |
79 local read_password = prosodyctl.read_password; | |
80 local call_luarocks = prosodyctl.call_luarocks; | 66 local call_luarocks = prosodyctl.call_luarocks; |
67 local error_messages = prosodyctl.error_messages; | |
81 | 68 |
82 local jid_split = require "util.jid".prepped_split; | 69 local jid_split = require "util.jid".prepped_split; |
83 | 70 |
84 local prosodyctl_timeout = (configmanager.get("*", "prosodyctl_timeout") or 5) * 2; | 71 local prosodyctl_timeout = (configmanager.get("*", "prosodyctl_timeout") or 5) * 2; |
85 ----------------------- | 72 ----------------------- |
548 | 535 |
549 if ok then return 0; end | 536 if ok then return 0; end |
550 | 537 |
551 show_message(error_messages[msg]) | 538 show_message(error_messages[msg]) |
552 return 1; | 539 return 1; |
553 end | |
554 | |
555 local openssl; | |
556 | |
557 local cert_commands = {}; | |
558 | |
559 -- If a file already exists, ask if the user wants to use it or replace it | |
560 -- Backups the old file if replaced | |
561 local function use_existing(filename) | |
562 local attrs = lfs.attributes(filename); | |
563 if attrs then | |
564 if show_yesno(filename .. " exists, do you want to replace it? [y/n]") then | |
565 local backup = filename..".bkp~"..os.date("%FT%T", attrs.change); | |
566 os.rename(filename, backup); | |
567 show_message(filename.." backed up to "..backup); | |
568 else | |
569 -- Use the existing file | |
570 return true; | |
571 end | |
572 end | |
573 end | |
574 | |
575 local have_pposix, pposix = pcall(require, "util.pposix"); | |
576 local cert_basedir = prosody.paths.data == "." and "./certs" or prosody.paths.data; | |
577 if have_pposix and pposix.getuid() == 0 then | |
578 -- FIXME should be enough to check if this directory is writable | |
579 local cert_dir = configmanager.get("*", "certificates") or "certs"; | |
580 cert_basedir = configmanager.resolve_relative_path(prosody.paths.config, cert_dir); | |
581 end | |
582 | |
583 function cert_commands.config(arg) | |
584 if #arg >= 1 and arg[1] ~= "--help" then | |
585 local conf_filename = cert_basedir .. "/" .. arg[1] .. ".cnf"; | |
586 if use_existing(conf_filename) then | |
587 return nil, conf_filename; | |
588 end | |
589 local distinguished_name; | |
590 if arg[#arg]:find("^/") then | |
591 distinguished_name = table.remove(arg); | |
592 end | |
593 local conf = openssl.config.new(); | |
594 conf:from_prosody(prosody.hosts, configmanager, arg); | |
595 if distinguished_name then | |
596 local dn = {}; | |
597 for k, v in distinguished_name:gmatch("/([^=/]+)=([^/]+)") do | |
598 table.insert(dn, k); | |
599 dn[k] = v; | |
600 end | |
601 conf.distinguished_name = dn; | |
602 else | |
603 show_message("Please provide details to include in the certificate config file."); | |
604 show_message("Leave the field empty to use the default value or '.' to exclude the field.") | |
605 for _, k in ipairs(openssl._DN_order) do | |
606 local v = conf.distinguished_name[k]; | |
607 if v then | |
608 local nv = nil; | |
609 if k == "commonName" then | |
610 v = arg[1] | |
611 elseif k == "emailAddress" then | |
612 v = "xmpp@" .. arg[1]; | |
613 elseif k == "countryName" then | |
614 local tld = arg[1]:match"%.([a-z]+)$"; | |
615 if tld and #tld == 2 and tld ~= "uk" then | |
616 v = tld:upper(); | |
617 end | |
618 end | |
619 nv = show_prompt(("%s (%s):"):format(k, nv or v)); | |
620 nv = (not nv or nv == "") and v or nv; | |
621 if nv:find"[\192-\252][\128-\191]+" then | |
622 conf.req.string_mask = "utf8only" | |
623 end | |
624 conf.distinguished_name[k] = nv ~= "." and nv or nil; | |
625 end | |
626 end | |
627 end | |
628 local conf_file, err = io.open(conf_filename, "w"); | |
629 if not conf_file then | |
630 show_warning("Could not open OpenSSL config file for writing"); | |
631 show_warning(err); | |
632 os.exit(1); | |
633 end | |
634 conf_file:write(conf:serialize()); | |
635 conf_file:close(); | |
636 print(""); | |
637 show_message("Config written to " .. conf_filename); | |
638 return nil, conf_filename; | |
639 else | |
640 show_usage("cert config HOSTNAME [HOSTNAME+]", "Builds a certificate config file covering the supplied hostname(s)") | |
641 end | |
642 end | |
643 | |
644 function cert_commands.key(arg) | |
645 if #arg >= 1 and arg[1] ~= "--help" then | |
646 local key_filename = cert_basedir .. "/" .. arg[1] .. ".key"; | |
647 if use_existing(key_filename) then | |
648 return nil, key_filename; | |
649 end | |
650 os.remove(key_filename); -- This file, if it exists is unlikely to have write permissions | |
651 local key_size = tonumber(arg[2] or show_prompt("Choose key size (2048):") or 2048); | |
652 local old_umask = pposix.umask("0377"); | |
653 if openssl.genrsa{out=key_filename, key_size} then | |
654 os.execute(("chmod 400 '%s'"):format(key_filename)); | |
655 show_message("Key written to ".. key_filename); | |
656 pposix.umask(old_umask); | |
657 return nil, key_filename; | |
658 end | |
659 show_message("There was a problem, see OpenSSL output"); | |
660 else | |
661 show_usage("cert key HOSTNAME <bits>", "Generates a RSA key named HOSTNAME.key\n " | |
662 .."Prompts for a key size if none given") | |
663 end | |
664 end | |
665 | |
666 function cert_commands.request(arg) | |
667 if #arg >= 1 and arg[1] ~= "--help" then | |
668 local req_filename = cert_basedir .. "/" .. arg[1] .. ".req"; | |
669 if use_existing(req_filename) then | |
670 return nil, req_filename; | |
671 end | |
672 local _, key_filename = cert_commands.key({arg[1]}); | |
673 local _, conf_filename = cert_commands.config(arg); | |
674 if openssl.req{new=true, key=key_filename, utf8=true, sha256=true, config=conf_filename, out=req_filename} then | |
675 show_message("Certificate request written to ".. req_filename); | |
676 else | |
677 show_message("There was a problem, see OpenSSL output"); | |
678 end | |
679 else | |
680 show_usage("cert request HOSTNAME [HOSTNAME+]", "Generates a certificate request for the supplied hostname(s)") | |
681 end | |
682 end | |
683 | |
684 function cert_commands.generate(arg) | |
685 if #arg >= 1 and arg[1] ~= "--help" then | |
686 local cert_filename = cert_basedir .. "/" .. arg[1] .. ".crt"; | |
687 if use_existing(cert_filename) then | |
688 return nil, cert_filename; | |
689 end | |
690 local _, key_filename = cert_commands.key({arg[1]}); | |
691 local _, conf_filename = cert_commands.config(arg); | |
692 if key_filename and conf_filename and cert_filename | |
693 and openssl.req{new=true, x509=true, nodes=true, key=key_filename, | |
694 days=365, sha256=true, utf8=true, config=conf_filename, out=cert_filename} then | |
695 show_message("Certificate written to ".. cert_filename); | |
696 print(); | |
697 else | |
698 show_message("There was a problem, see OpenSSL output"); | |
699 end | |
700 else | |
701 show_usage("cert generate HOSTNAME [HOSTNAME+]", "Generates a self-signed certificate for the current hostname(s)") | |
702 end | |
703 end | |
704 | |
705 local function sh_esc(s) | |
706 return "'" .. s:gsub("'", "'\\''") .. "'"; | |
707 end | |
708 | |
709 local function copy(from, to, umask, owner, group) | |
710 local old_umask = umask and pposix.umask(umask); | |
711 local attrs = lfs.attributes(to); | |
712 if attrs then -- Move old file out of the way | |
713 local backup = to..".bkp~"..os.date("%FT%T", attrs.change); | |
714 os.rename(to, backup); | |
715 end | |
716 -- FIXME friendlier error handling, maybe move above backup back? | |
717 local input = assert(io.open(from)); | |
718 local output = assert(io.open(to, "w")); | |
719 local data = input:read(2^11); | |
720 while data and output:write(data) do | |
721 data = input:read(2^11); | |
722 end | |
723 assert(input:close()); | |
724 assert(output:close()); | |
725 if not prosody.installed then | |
726 -- FIXME this is possibly specific to GNU chown | |
727 os.execute(("chown -c --reference=%s %s"):format(sh_esc(cert_basedir), sh_esc(to))); | |
728 elseif owner and group then | |
729 local ok = os.execute(("chown %s:%s %s"):format(sh_esc(owner), sh_esc(group), sh_esc(to))); | |
730 assert(ok == true or ok == 0, "Failed to change ownership of "..to); | |
731 end | |
732 if old_umask then pposix.umask(old_umask); end | |
733 return true; | |
734 end | |
735 | |
736 function cert_commands.import(arg) | |
737 local hostnames = {}; | |
738 -- Move hostname arguments out of arg, the rest should be a list of paths | |
739 while arg[1] and prosody.hosts[ arg[1] ] do | |
740 table.insert(hostnames, table.remove(arg, 1)); | |
741 end | |
742 if hostnames[1] == nil then | |
743 local domains = os.getenv"RENEWED_DOMAINS"; -- Set if invoked via certbot | |
744 if domains then | |
745 for host in domains:gmatch("%S+") do | |
746 table.insert(hostnames, host); | |
747 end | |
748 else | |
749 for host in pairs(prosody.hosts) do | |
750 if host ~= "*" and configmanager.get(host, "enabled") ~= false then | |
751 table.insert(hostnames, host); | |
752 end | |
753 end | |
754 end | |
755 end | |
756 if not arg[1] or arg[1] == "--help" then -- Probably forgot the path | |
757 show_usage("cert import [HOSTNAME+] /path/to/certs [/other/paths/]+", | |
758 "Copies certificates to "..cert_basedir); | |
759 return 1; | |
760 end | |
761 local owner, group; | |
762 if pposix.getuid() == 0 then -- We need root to change ownership | |
763 owner = configmanager.get("*", "prosody_user") or "prosody"; | |
764 group = configmanager.get("*", "prosody_group") or owner; | |
765 end | |
766 local cm = require "core.certmanager"; | |
767 local imported = {}; | |
768 for _, host in ipairs(hostnames) do | |
769 for _, dir in ipairs(arg) do | |
770 local paths = cm.find_cert(dir, host); | |
771 if paths then | |
772 copy(paths.certificate, cert_basedir .. "/" .. host .. ".crt", nil, owner, group); | |
773 copy(paths.key, cert_basedir .. "/" .. host .. ".key", "0377", owner, group); | |
774 table.insert(imported, host); | |
775 else | |
776 -- TODO Say where we looked | |
777 show_warning("No certificate for host "..host.." found :("); | |
778 end | |
779 -- TODO Additional checks | |
780 -- Certificate names matches the hostname | |
781 -- Private key matches public key in certificate | |
782 end | |
783 end | |
784 if imported[1] then | |
785 show_message("Imported certificate and key for hosts "..table.concat(imported, ", ")); | |
786 local ok, err = prosodyctl.reload(); | |
787 if not ok and err ~= "not-running" then | |
788 show_message(error_messages[err]); | |
789 end | |
790 else | |
791 show_warning("No certificates imported :("); | |
792 return 1; | |
793 end | |
794 end | |
795 | |
796 function commands.cert(arg) | |
797 if #arg >= 1 and arg[1] ~= "--help" then | |
798 openssl = require "util.openssl"; | |
799 lfs = require "lfs"; | |
800 local cert_dir_attrs = lfs.attributes(cert_basedir); | |
801 if not cert_dir_attrs then | |
802 show_warning("The directory "..cert_basedir.." does not exist"); | |
803 return 1; -- TODO Should we create it? | |
804 end | |
805 local uid = pposix.getuid(); | |
806 if uid ~= 0 and uid ~= cert_dir_attrs.uid then | |
807 show_warning("The directory "..cert_basedir.." is not owned by the current user, won't be able to write files to it"); | |
808 return 1; | |
809 elseif not cert_dir_attrs.permissions then -- COMPAT with LuaFilesystem < 1.6.2 (hey CentOS!) | |
810 show_message("Unable to check permissions on "..cert_basedir.." (LuaFilesystem 1.6.2+ required)"); | |
811 show_message("Please confirm that Prosody (and only Prosody) can write to this directory)"); | |
812 elseif cert_dir_attrs.permissions:match("^%.w..%-..%-.$") then | |
813 show_warning("The directory "..cert_basedir.." not only writable by its owner"); | |
814 return 1; | |
815 end | |
816 local subcmd = table.remove(arg, 1); | |
817 if type(cert_commands[subcmd]) == "function" then | |
818 if subcmd ~= "import" then -- hostnames are optional for import | |
819 if not arg[1] then | |
820 show_message"You need to supply at least one hostname" | |
821 arg = { "--help" }; | |
822 end | |
823 if arg[1] ~= "--help" and not prosody.hosts[arg[1]] then | |
824 show_message(error_messages["no-such-host"]); | |
825 return 1; | |
826 end | |
827 end | |
828 return cert_commands[subcmd](arg); | |
829 elseif subcmd == "check" then | |
830 return commands.check({"certs"}); | |
831 end | |
832 end | |
833 show_usage("cert config|request|generate|key|import", "Helpers for generating X.509 certificates and keys.") | |
834 for _, cmd in pairs(cert_commands) do | |
835 print() | |
836 cmd{ "--help" } | |
837 end | |
838 end | |
839 | |
840 function commands.check(arg) | |
841 if arg[1] == "--help" then | |
842 show_usage([[check]], [[Perform basic checks on your Prosody installation]]); | |
843 return 1; | |
844 end | |
845 local what = table.remove(arg, 1); | |
846 local set = require "util.set"; | |
847 local it = require "util.iterators"; | |
848 local ok = true; | |
849 local function disabled_hosts(host, conf) return host ~= "*" and conf.enabled ~= false; end | |
850 local function enabled_hosts() return it.filter(disabled_hosts, pairs(configmanager.getconfig())); end | |
851 if not (what == nil or what == "disabled" or what == "config" or what == "dns" or what == "certs") then | |
852 show_warning("Don't know how to check '%s'. Try one of 'config', 'dns', 'certs' or 'disabled'.", what); | |
853 return 1; | |
854 end | |
855 if not what or what == "disabled" then | |
856 local disabled_hosts_set = set.new(); | |
857 for host, host_options in it.filter("*", pairs(configmanager.getconfig())) do | |
858 if host_options.enabled == false then | |
859 disabled_hosts_set:add(host); | |
860 end | |
861 end | |
862 if not disabled_hosts_set:empty() then | |
863 local msg = "Checks will be skipped for these disabled hosts: %s"; | |
864 if what then msg = "These hosts are disabled: %s"; end | |
865 show_warning(msg, tostring(disabled_hosts_set)); | |
866 if what then return 0; end | |
867 print"" | |
868 end | |
869 end | |
870 if not what or what == "config" then | |
871 print("Checking config..."); | |
872 local deprecated = set.new({ | |
873 "bosh_ports", "disallow_s2s", "no_daemonize", "anonymous_login", "require_encryption", | |
874 "vcard_compatibility", "cross_domain_bosh", "cross_domain_websocket", "daemonize", | |
875 }); | |
876 local known_global_options = set.new({ | |
877 "pidfile", "log", "plugin_paths", "prosody_user", "prosody_group", "daemonize", | |
878 "umask", "prosodyctl_timeout", "use_ipv6", "use_libevent", "network_settings", | |
879 "network_backend", "http_default_host", | |
880 "statistics_interval", "statistics", "statistics_config", | |
881 }); | |
882 local config = configmanager.getconfig(); | |
883 -- Check that we have any global options (caused by putting a host at the top) | |
884 if it.count(it.filter("log", pairs(config["*"]))) == 0 then | |
885 ok = false; | |
886 print(""); | |
887 print(" No global options defined. Perhaps you have put a host definition at the top") | |
888 print(" of the config file? They should be at the bottom, see https://prosody.im/doc/configure#overview"); | |
889 end | |
890 if it.count(enabled_hosts()) == 0 then | |
891 ok = false; | |
892 print(""); | |
893 if it.count(it.filter("*", pairs(config))) == 0 then | |
894 print(" No hosts are defined, please add at least one VirtualHost section") | |
895 elseif config["*"]["enabled"] == false then | |
896 print(" No hosts are enabled. Remove enabled = false from the global section or put enabled = true under at least one VirtualHost section") | |
897 else | |
898 print(" All hosts are disabled. Remove enabled = false from at least one VirtualHost section") | |
899 end | |
900 end | |
901 if not config["*"].modules_enabled then | |
902 print(" No global modules_enabled is set?"); | |
903 local suggested_global_modules; | |
904 for host, options in enabled_hosts() do --luacheck: ignore 213/host | |
905 if not options.component_module and options.modules_enabled then | |
906 suggested_global_modules = set.intersection(suggested_global_modules or set.new(options.modules_enabled), set.new(options.modules_enabled)); | |
907 end | |
908 end | |
909 if suggested_global_modules and not suggested_global_modules:empty() then | |
910 print(" Consider moving these modules into modules_enabled in the global section:") | |
911 print(" "..tostring(suggested_global_modules / function (x) return ("%q"):format(x) end)); | |
912 end | |
913 print(); | |
914 end | |
915 | |
916 do -- Check for modules enabled both normally and as components | |
917 local modules = set.new(config["*"]["modules_enabled"]); | |
918 for host, options in enabled_hosts() do | |
919 local component_module = options.component_module; | |
920 if component_module and modules:contains(component_module) then | |
921 print((" mod_%s is enabled both in modules_enabled and as Component %q %q"):format(component_module, host, component_module)); | |
922 print(" This means the service is enabled on all VirtualHosts as well as the Component."); | |
923 print(" Are you sure this what you want? It may cause unexpected behaviour."); | |
924 end | |
925 end | |
926 end | |
927 | |
928 -- Check for global options under hosts | |
929 local global_options = set.new(it.to_array(it.keys(config["*"]))); | |
930 local deprecated_global_options = set.intersection(global_options, deprecated); | |
931 if not deprecated_global_options:empty() then | |
932 print(""); | |
933 print(" You have some deprecated options in the global section:"); | |
934 print(" "..tostring(deprecated_global_options)) | |
935 ok = false; | |
936 end | |
937 for host, options in it.filter(function (h) return h ~= "*" end, pairs(configmanager.getconfig())) do | |
938 local host_options = set.new(it.to_array(it.keys(options))); | |
939 local misplaced_options = set.intersection(host_options, known_global_options); | |
940 for name in pairs(options) do | |
941 if name:match("^interfaces?") | |
942 or name:match("_ports?$") or name:match("_interfaces?$") | |
943 or (name:match("_ssl$") and not name:match("^[cs]2s_ssl$")) then | |
944 misplaced_options:add(name); | |
945 end | |
946 end | |
947 if not misplaced_options:empty() then | |
948 ok = false; | |
949 print(""); | |
950 local n = it.count(misplaced_options); | |
951 print(" You have "..n.." option"..(n>1 and "s " or " ").."set under "..host.." that should be"); | |
952 print(" in the global section of the config file, above any VirtualHost or Component definitions,") | |
953 print(" see https://prosody.im/doc/configure#overview for more information.") | |
954 print(""); | |
955 print(" You need to move the following option"..(n>1 and "s" or "")..": "..table.concat(it.to_array(misplaced_options), ", ")); | |
956 end | |
957 end | |
958 for host, options in enabled_hosts() do | |
959 local host_options = set.new(it.to_array(it.keys(options))); | |
960 local subdomain = host:match("^[^.]+"); | |
961 if not(host_options:contains("component_module")) and (subdomain == "jabber" or subdomain == "xmpp" | |
962 or subdomain == "chat" or subdomain == "im") then | |
963 print(""); | |
964 print(" Suggestion: If "..host.. " is a new host with no real users yet, consider renaming it now to"); | |
965 print(" "..host:gsub("^[^.]+%.", "")..". You can use SRV records to redirect XMPP clients and servers to "..host.."."); | |
966 print(" For more information see: https://prosody.im/doc/dns"); | |
967 end | |
968 end | |
969 local all_modules = set.new(config["*"].modules_enabled); | |
970 local all_options = set.new(it.to_array(it.keys(config["*"]))); | |
971 for host in enabled_hosts() do | |
972 all_options:include(set.new(it.to_array(it.keys(config[host])))); | |
973 all_modules:include(set.new(config[host].modules_enabled)); | |
974 end | |
975 for mod in all_modules do | |
976 if mod:match("^mod_") then | |
977 print(""); | |
978 print(" Modules in modules_enabled should not have the 'mod_' prefix included."); | |
979 print(" Change '"..mod.."' to '"..mod:match("^mod_(.*)").."'."); | |
980 elseif mod:match("^auth_") then | |
981 print(""); | |
982 print(" Authentication modules should not be added to modules_enabled,"); | |
983 print(" but be specified in the 'authentication' option."); | |
984 print(" Remove '"..mod.."' from modules_enabled and instead add"); | |
985 print(" authentication = '"..mod:match("^auth_(.*)").."'"); | |
986 print(" For more information see https://prosody.im/doc/authentication"); | |
987 elseif mod:match("^storage_") then | |
988 print(""); | |
989 print(" storage modules should not be added to modules_enabled,"); | |
990 print(" but be specified in the 'storage' option."); | |
991 print(" Remove '"..mod.."' from modules_enabled and instead add"); | |
992 print(" storage = '"..mod:match("^storage_(.*)").."'"); | |
993 print(" For more information see https://prosody.im/doc/storage"); | |
994 end | |
995 end | |
996 if all_modules:contains("vcard") and all_modules:contains("vcard_legacy") then | |
997 print(""); | |
998 print(" Both mod_vcard_legacy and mod_vcard are enabled but they conflict"); | |
999 print(" with each other. Remove one."); | |
1000 end | |
1001 if all_modules:contains("pep") and all_modules:contains("pep_simple") then | |
1002 print(""); | |
1003 print(" Both mod_pep_simple and mod_pep are enabled but they conflict"); | |
1004 print(" with each other. Remove one."); | |
1005 end | |
1006 for host, host_config in pairs(config) do --luacheck: ignore 213/host | |
1007 if type(rawget(host_config, "storage")) == "string" and rawget(host_config, "default_storage") then | |
1008 print(""); | |
1009 print(" The 'default_storage' option is not needed if 'storage' is set to a string."); | |
1010 break; | |
1011 end | |
1012 end | |
1013 local require_encryption = set.intersection(all_options, set.new({ | |
1014 "require_encryption", "c2s_require_encryption", "s2s_require_encryption" | |
1015 })):empty(); | |
1016 local ssl = dependencies.softreq"ssl"; | |
1017 if not ssl then | |
1018 if not require_encryption then | |
1019 print(""); | |
1020 print(" You require encryption but LuaSec is not available."); | |
1021 print(" Connections will fail."); | |
1022 ok = false; | |
1023 end | |
1024 elseif not ssl.loadcertificate then | |
1025 if all_options:contains("s2s_secure_auth") then | |
1026 print(""); | |
1027 print(" You have set s2s_secure_auth but your version of LuaSec does "); | |
1028 print(" not support certificate validation, so all s2s connections will"); | |
1029 print(" fail."); | |
1030 ok = false; | |
1031 elseif all_options:contains("s2s_secure_domains") then | |
1032 local secure_domains = set.new(); | |
1033 for host in enabled_hosts() do | |
1034 if config[host].s2s_secure_auth == true then | |
1035 secure_domains:add("*"); | |
1036 else | |
1037 secure_domains:include(set.new(config[host].s2s_secure_domains)); | |
1038 end | |
1039 end | |
1040 if not secure_domains:empty() then | |
1041 print(""); | |
1042 print(" You have set s2s_secure_domains but your version of LuaSec does "); | |
1043 print(" not support certificate validation, so s2s connections to/from "); | |
1044 print(" these domains will fail."); | |
1045 ok = false; | |
1046 end | |
1047 end | |
1048 elseif require_encryption and not all_modules:contains("tls") then | |
1049 print(""); | |
1050 print(" You require encryption but mod_tls is not enabled."); | |
1051 print(" Connections will fail."); | |
1052 ok = false; | |
1053 end | |
1054 | |
1055 print("Done.\n"); | |
1056 end | |
1057 if not what or what == "dns" then | |
1058 local dns = require "net.dns"; | |
1059 local idna = require "util.encodings".idna; | |
1060 local ip = require "util.ip"; | |
1061 local c2s_ports = set.new(configmanager.get("*", "c2s_ports") or {5222}); | |
1062 local s2s_ports = set.new(configmanager.get("*", "s2s_ports") or {5269}); | |
1063 | |
1064 local c2s_srv_required, s2s_srv_required; | |
1065 if not c2s_ports:contains(5222) then | |
1066 c2s_srv_required = true; | |
1067 end | |
1068 if not s2s_ports:contains(5269) then | |
1069 s2s_srv_required = true; | |
1070 end | |
1071 | |
1072 local problem_hosts = set.new(); | |
1073 | |
1074 local external_addresses, internal_addresses = set.new(), set.new(); | |
1075 | |
1076 local fqdn = socket.dns.tohostname(socket.dns.gethostname()); | |
1077 if fqdn then | |
1078 do | |
1079 local res = dns.lookup(idna.to_ascii(fqdn), "A"); | |
1080 if res then | |
1081 for _, record in ipairs(res) do | |
1082 external_addresses:add(record.a); | |
1083 end | |
1084 end | |
1085 end | |
1086 do | |
1087 local res = dns.lookup(idna.to_ascii(fqdn), "AAAA"); | |
1088 if res then | |
1089 for _, record in ipairs(res) do | |
1090 external_addresses:add(record.aaaa); | |
1091 end | |
1092 end | |
1093 end | |
1094 end | |
1095 | |
1096 local local_addresses = require"util.net".local_addresses() or {}; | |
1097 | |
1098 for addr in it.values(local_addresses) do | |
1099 if not ip.new_ip(addr).private then | |
1100 external_addresses:add(addr); | |
1101 else | |
1102 internal_addresses:add(addr); | |
1103 end | |
1104 end | |
1105 | |
1106 if external_addresses:empty() then | |
1107 print(""); | |
1108 print(" Failed to determine the external addresses of this server. Checks may be inaccurate."); | |
1109 c2s_srv_required, s2s_srv_required = true, true; | |
1110 end | |
1111 | |
1112 local v6_supported = not not socket.tcp6; | |
1113 | |
1114 for jid, host_options in enabled_hosts() do | |
1115 local all_targets_ok, some_targets_ok = true, false; | |
1116 local node, host = jid_split(jid); | |
1117 | |
1118 local modules, component_module = modulemanager.get_modules_for_host(host); | |
1119 if component_module then | |
1120 modules:add(component_module); | |
1121 end | |
1122 | |
1123 local is_component = not not host_options.component_module; | |
1124 print("Checking DNS for "..(is_component and "component" or "host").." "..jid.."..."); | |
1125 if node then | |
1126 print("Only the domain part ("..host..") is used in DNS.") | |
1127 end | |
1128 local target_hosts = set.new(); | |
1129 if modules:contains("c2s") then | |
1130 local res = dns.lookup("_xmpp-client._tcp."..idna.to_ascii(host)..".", "SRV"); | |
1131 if res then | |
1132 for _, record in ipairs(res) do | |
1133 target_hosts:add(record.srv.target); | |
1134 if not c2s_ports:contains(record.srv.port) then | |
1135 print(" SRV target "..record.srv.target.." contains unknown client port: "..record.srv.port); | |
1136 end | |
1137 end | |
1138 else | |
1139 if c2s_srv_required then | |
1140 print(" No _xmpp-client SRV record found for "..host..", but it looks like you need one."); | |
1141 all_targets_ok = false; | |
1142 else | |
1143 target_hosts:add(host); | |
1144 end | |
1145 end | |
1146 end | |
1147 if modules:contains("s2s") then | |
1148 local res = dns.lookup("_xmpp-server._tcp."..idna.to_ascii(host)..".", "SRV"); | |
1149 if res then | |
1150 for _, record in ipairs(res) do | |
1151 target_hosts:add(record.srv.target); | |
1152 if not s2s_ports:contains(record.srv.port) then | |
1153 print(" SRV target "..record.srv.target.." contains unknown server port: "..record.srv.port); | |
1154 end | |
1155 end | |
1156 else | |
1157 if s2s_srv_required then | |
1158 print(" No _xmpp-server SRV record found for "..host..", but it looks like you need one."); | |
1159 all_targets_ok = false; | |
1160 else | |
1161 target_hosts:add(host); | |
1162 end | |
1163 end | |
1164 end | |
1165 if target_hosts:empty() then | |
1166 target_hosts:add(host); | |
1167 end | |
1168 | |
1169 if target_hosts:contains("localhost") then | |
1170 print(" Target 'localhost' cannot be accessed from other servers"); | |
1171 target_hosts:remove("localhost"); | |
1172 end | |
1173 | |
1174 if modules:contains("proxy65") then | |
1175 local proxy65_target = configmanager.get(host, "proxy65_address") or host; | |
1176 if type(proxy65_target) == "string" then | |
1177 local A, AAAA = dns.lookup(idna.to_ascii(proxy65_target), "A"), dns.lookup(idna.to_ascii(proxy65_target), "AAAA"); | |
1178 local prob = {}; | |
1179 if not A then | |
1180 table.insert(prob, "A"); | |
1181 end | |
1182 if v6_supported and not AAAA then | |
1183 table.insert(prob, "AAAA"); | |
1184 end | |
1185 if #prob > 0 then | |
1186 print(" File transfer proxy "..proxy65_target.." has no "..table.concat(prob, "/") | |
1187 .." record. Create one or set 'proxy65_address' to the correct host/IP."); | |
1188 end | |
1189 else | |
1190 print(" proxy65_address for "..host.." should be set to a string, unable to perform DNS check"); | |
1191 end | |
1192 end | |
1193 | |
1194 for target_host in target_hosts do | |
1195 local host_ok_v4, host_ok_v6; | |
1196 do | |
1197 local res = dns.lookup(idna.to_ascii(target_host), "A"); | |
1198 if res then | |
1199 for _, record in ipairs(res) do | |
1200 if external_addresses:contains(record.a) then | |
1201 some_targets_ok = true; | |
1202 host_ok_v4 = true; | |
1203 elseif internal_addresses:contains(record.a) then | |
1204 host_ok_v4 = true; | |
1205 some_targets_ok = true; | |
1206 print(" "..target_host.." A record points to internal address, external connections might fail"); | |
1207 else | |
1208 print(" "..target_host.." A record points to unknown address "..record.a); | |
1209 all_targets_ok = false; | |
1210 end | |
1211 end | |
1212 end | |
1213 end | |
1214 do | |
1215 local res = dns.lookup(idna.to_ascii(target_host), "AAAA"); | |
1216 if res then | |
1217 for _, record in ipairs(res) do | |
1218 if external_addresses:contains(record.aaaa) then | |
1219 some_targets_ok = true; | |
1220 host_ok_v6 = true; | |
1221 elseif internal_addresses:contains(record.aaaa) then | |
1222 host_ok_v6 = true; | |
1223 some_targets_ok = true; | |
1224 print(" "..target_host.." AAAA record points to internal address, external connections might fail"); | |
1225 else | |
1226 print(" "..target_host.." AAAA record points to unknown address "..record.aaaa); | |
1227 all_targets_ok = false; | |
1228 end | |
1229 end | |
1230 end | |
1231 end | |
1232 | |
1233 local bad_protos = {} | |
1234 if not host_ok_v4 then | |
1235 table.insert(bad_protos, "IPv4"); | |
1236 end | |
1237 if not host_ok_v6 then | |
1238 table.insert(bad_protos, "IPv6"); | |
1239 end | |
1240 if #bad_protos > 0 then | |
1241 print(" Host "..target_host.." does not seem to resolve to this server ("..table.concat(bad_protos, "/")..")"); | |
1242 end | |
1243 if host_ok_v6 and not v6_supported then | |
1244 print(" Host "..target_host.." has AAAA records, but your version of LuaSocket does not support IPv6."); | |
1245 print(" Please see https://prosody.im/doc/ipv6 for more information."); | |
1246 end | |
1247 end | |
1248 if not all_targets_ok then | |
1249 print(" "..(some_targets_ok and "Only some" or "No").." targets for "..host.." appear to resolve to this server."); | |
1250 if is_component then | |
1251 print(" DNS records are necessary if you want users on other servers to access this component."); | |
1252 end | |
1253 problem_hosts:add(host); | |
1254 end | |
1255 print(""); | |
1256 end | |
1257 if not problem_hosts:empty() then | |
1258 print(""); | |
1259 print("For more information about DNS configuration please see https://prosody.im/doc/dns"); | |
1260 print(""); | |
1261 ok = false; | |
1262 end | |
1263 end | |
1264 if not what or what == "certs" then | |
1265 local cert_ok; | |
1266 print"Checking certificates..." | |
1267 local x509_verify_identity = require"util.x509".verify_identity; | |
1268 local create_context = require "core.certmanager".create_context; | |
1269 local ssl = dependencies.softreq"ssl"; | |
1270 -- local datetime_parse = require"util.datetime".parse_x509; | |
1271 local load_cert = ssl and ssl.loadcertificate; | |
1272 -- or ssl.cert_from_pem | |
1273 if not ssl then | |
1274 print("LuaSec not available, can't perform certificate checks") | |
1275 if what == "certs" then cert_ok = false end | |
1276 elseif not load_cert then | |
1277 print("This version of LuaSec (" .. ssl._VERSION .. ") does not support certificate checking"); | |
1278 cert_ok = false | |
1279 else | |
1280 local function skip_bare_jid_hosts(host) | |
1281 if jid_split(host) then | |
1282 -- See issue #779 | |
1283 return false; | |
1284 end | |
1285 return true; | |
1286 end | |
1287 for host in it.filter(skip_bare_jid_hosts, enabled_hosts()) do | |
1288 print("Checking certificate for "..host); | |
1289 -- First, let's find out what certificate this host uses. | |
1290 local host_ssl_config = configmanager.rawget(host, "ssl") | |
1291 or configmanager.rawget(host:match("%.(.*)"), "ssl"); | |
1292 local global_ssl_config = configmanager.rawget("*", "ssl"); | |
1293 local ok, err, ssl_config = create_context(host, "server", host_ssl_config, global_ssl_config); | |
1294 if not ok then | |
1295 print(" Error: "..err); | |
1296 cert_ok = false | |
1297 elseif not ssl_config.certificate then | |
1298 print(" No 'certificate' found for "..host) | |
1299 cert_ok = false | |
1300 elseif not ssl_config.key then | |
1301 print(" No 'key' found for "..host) | |
1302 cert_ok = false | |
1303 else | |
1304 local key, err = io.open(ssl_config.key); -- Permissions check only | |
1305 if not key then | |
1306 print(" Could not open "..ssl_config.key..": "..err); | |
1307 cert_ok = false | |
1308 else | |
1309 key:close(); | |
1310 end | |
1311 local cert_fh, err = io.open(ssl_config.certificate); -- Load the file. | |
1312 if not cert_fh then | |
1313 print(" Could not open "..ssl_config.certificate..": "..err); | |
1314 cert_ok = false | |
1315 else | |
1316 print(" Certificate: "..ssl_config.certificate) | |
1317 local cert = load_cert(cert_fh:read"*a"); cert_fh:close(); | |
1318 if not cert:validat(os.time()) then | |
1319 print(" Certificate has expired.") | |
1320 cert_ok = false | |
1321 elseif not cert:validat(os.time() + 86400) then | |
1322 print(" Certificate expires within one day.") | |
1323 cert_ok = false | |
1324 elseif not cert:validat(os.time() + 86400*7) then | |
1325 print(" Certificate expires within one week.") | |
1326 elseif not cert:validat(os.time() + 86400*31) then | |
1327 print(" Certificate expires within one month.") | |
1328 end | |
1329 if configmanager.get(host, "component_module") == nil | |
1330 and not x509_verify_identity(host, "_xmpp-client", cert) then | |
1331 print(" Not valid for client connections to "..host..".") | |
1332 cert_ok = false | |
1333 end | |
1334 if (not (configmanager.get(host, "anonymous_login") | |
1335 or configmanager.get(host, "authentication") == "anonymous")) | |
1336 and not x509_verify_identity(host, "_xmpp-server", cert) then | |
1337 print(" Not valid for server-to-server connections to "..host..".") | |
1338 cert_ok = false | |
1339 end | |
1340 end | |
1341 end | |
1342 end | |
1343 end | |
1344 if cert_ok == false then | |
1345 print("") | |
1346 print("For more information about certificates please see https://prosody.im/doc/certificates"); | |
1347 ok = false | |
1348 end | |
1349 print("") | |
1350 end | |
1351 if not ok then | |
1352 print("Problems found, see above."); | |
1353 else | |
1354 print("All checks passed, congratulations!"); | |
1355 end | |
1356 return ok and 0 or 2; | |
1357 end | |
1358 | |
1359 function commands.shell(arg) | |
1360 require "util.prosodyctl.shell".start(arg); | |
1361 end | 540 end |
1362 | 541 |
1363 --------------------- | 542 --------------------- |
1364 | 543 |
1365 local async = require "util.async"; | 544 local async = require "util.async"; |
1406 show_message("Failed to execute command: "..error_messages[ret]); | 585 show_message("Failed to execute command: "..error_messages[ret]); |
1407 os.exit(1); -- :( | 586 os.exit(1); -- :( |
1408 end | 587 end |
1409 end | 588 end |
1410 | 589 |
590 if not commands[command] then | |
591 local ok, command_module = pcall(require, "util.prosodyctl."..command); | |
592 if ok and command_module[command] then | |
593 commands[command] = command_module[command]; | |
594 end | |
595 end | |
596 | |
1411 if not commands[command] then -- Show help for all commands | 597 if not commands[command] then -- Show help for all commands |
1412 function show_usage(usage, desc) | 598 function show_usage(usage, desc) |
1413 print(" "..usage); | 599 print(" "..usage); |
1414 print(" "..desc); | 600 print(" "..desc); |
1415 end | 601 end |