prosodyctl
changeset 10875 e5dee71d0ebb
parent 10862 efa49d484560
child 10909 709255e332d8
equal deleted inserted replaced
10874:3f1889608f3e 10875: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