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"; |