X-Git-Url: https://git.enpas.org/?a=blobdiff_plain;f=prosodyctl;h=df8c8e755312df4b20ba4869cdba6a1a8a0aadbe;hb=289498a056350129cacfe32d84feebcf1f61f816;hp=de7c09c56a1d2a06c67260ccdf5ca403d7bd19b4;hpb=ec4f0892eb007a340b64e9cdce528dcf2819b92b;p=prosody.git diff --git a/prosodyctl b/prosodyctl index de7c09c5..df8c8e75 100755 --- a/prosodyctl +++ b/prosodyctl @@ -220,6 +220,7 @@ local error_messages = setmetatable({ ["no-such-host"] = "The given hostname does not exist in the config"; ["unable-to-save-data"] = "Unable to store, perhaps you don't have permission?"; ["no-pidfile"] = "There is no 'pidfile' option in the configuration file, see http://prosody.im/doc/prosodyctl#pidfile for help"; + ["invalid-pidfile"] = "The 'pidfile' option in the configuration file is not a string, see http://prosody.im/doc/prosodyctl#pidfile for help"; ["no-posix"] = "The mod_posix module is not enabled in the Prosody config file, see http://prosody.im/doc/prosodyctl for more info"; ["no-such-method"] = "This module has no commands"; ["not-running"] = "Prosody is not running"; @@ -268,13 +269,14 @@ local show_yesno = prosodyctl.show_yesno; local show_prompt = prosodyctl.show_prompt; local read_password = prosodyctl.read_password; +local jid_split = require "util.jid".prepped_split; + local prosodyctl_timeout = (config.get("*", "prosodyctl_timeout") or 5) * 2; ----------------------- local commands = {}; local command = arg[1]; function commands.adduser(arg) - local jid_split = require "util.jid".split; if not arg[1] or arg[1] == "--help" then show_usage([[adduser JID]], [[Create the specified user account in Prosody]]); return 1; @@ -314,7 +316,6 @@ function commands.adduser(arg) end function commands.passwd(arg) - local jid_split = require "util.jid".split; if not arg[1] or arg[1] == "--help" then show_usage([[passwd JID]], [[Set the password for the specified user account in Prosody]]); return 1; @@ -354,7 +355,6 @@ function commands.passwd(arg) end function commands.deluser(arg) - local jid_split = require "util.jid".split; if not arg[1] or arg[1] == "--help" then show_usage([[deluser JID]], [[Permanently remove the specified user account from Prosody]]); return 1; @@ -373,7 +373,6 @@ function commands.deluser(arg) if not hosts[host] then show_warning("The host '%s' is not listed in the configuration file (or is not enabled).", host) - show_warning("The user will not be able to log in until this is changed."); hosts[host] = make_host(host); end @@ -414,7 +413,11 @@ function commands.start(arg) local ok, ret = prosodyctl.start(); if ok then - if config.get("*", "daemonize") ~= false then + local daemonize = config.get("*", "daemonize"); + if daemonize == nil then + daemonize = prosody.installed; + end + if daemonize then local i=1; while true do local ok, running = prosodyctl.isrunning(); @@ -687,7 +690,12 @@ function cert_commands.config(arg) conf.distinguished_name[k] = nv ~= "." and nv or nil; end end - local conf_file = io.open(conf_filename, "w"); + local conf_file, err = io.open(conf_filename, "w"); + if not conf_file then + show_warning("Could not open OpenSSL config file for writing"); + show_warning(err); + os.exit(1); + end conf_file:write(conf:serialize()); conf_file:close(); print(""); @@ -728,7 +736,7 @@ function cert_commands.request(arg) end local _, key_filename = cert_commands.key({arg[1]}); local _, conf_filename = cert_commands.config(arg); - if openssl.req{new=true, key=key_filename, utf8=true, config=conf_filename, out=req_filename} then + if openssl.req{new=true, key=key_filename, utf8=true, sha256=true, config=conf_filename, out=req_filename} then show_message("Certificate request written to ".. req_filename); else show_message("There was a problem, see OpenSSL output"); @@ -749,7 +757,7 @@ function cert_commands.generate(arg) local ret; if key_filename and conf_filename and cert_filename and openssl.req{new=true, x509=true, nodes=true, key=key_filename, - days=365, sha1=true, utf8=true, config=conf_filename, out=cert_filename} then + days=365, sha256=true, utf8=true, config=conf_filename, out=cert_filename} then show_message("Certificate written to ".. cert_filename); else show_message("There was a problem, see OpenSSL output"); @@ -780,12 +788,36 @@ function commands.cert(arg) end function commands.check(arg) + if arg[1] == "--help" then + show_usage([[check]], [[Perform basic checks on your Prosody installation]]); + return 1; + end local what = table.remove(arg, 1); local array, set = require "util.array", require "util.set"; local it = require "util.iterators"; local ok = true; + local function disabled_hosts(host, conf) return host ~= "*" and conf.enabled ~= false; end + local function enabled_hosts() return it.filter(disabled_hosts, pairs(config.getconfig())); end + if not what or what == "disabled" then + local disabled_hosts = set.new(); + for host, host_options in it.filter("*", pairs(config.getconfig())) do + if host_options.enabled == false then + disabled_hosts:add(host); + end + end + if not disabled_hosts:empty() then + local msg = "Checks will be skipped for these disabled hosts: %s"; + if what then msg = "These hosts are disabled: %s"; end + show_warning(msg, tostring(disabled_hosts)); + if what then return 0; end + print"" + end + end if not what or what == "config" then print("Checking config..."); + local deprecated = set.new({ + "bosh_ports", "disallow_s2s", "no_daemonize", "anonymous_login", + }); local known_global_options = set.new({ "pidfile", "log", "plugin_paths", "prosody_user", "prosody_group", "daemonize", "umask", "prosodyctl_timeout", "use_ipv6", "use_libevent", "network_settings" @@ -798,9 +830,27 @@ function commands.check(arg) print(" No global options defined. Perhaps you have put a host definition at the top") print(" of the config file? They should be at the bottom, see http://prosody.im/doc/configure#overview"); end + if it.count(enabled_hosts()) == 0 then + ok = false; + print(""); + if it.count(it.filter("*", pairs(config))) == 0 then + print(" No hosts are defined, please add at least one VirtualHost section") + elseif config["*"]["enabled"] == false then + print(" No hosts are enabled. Remove enabled = false from the global section or put enabled = true under at least one VirtualHost section") + else + print(" All hosts are disabled. Remove enabled = false from at least one VirtualHost section") + end + end -- Check for global options under hosts local global_options = set.new(it.to_array(it.keys(config["*"]))); - for host, options in it.filter("*", pairs(config)) do + local deprecated_global_options = set.intersection(global_options, deprecated); + if not deprecated_global_options:empty() then + print(""); + print(" You have some deprecated options in the global section:"); + print(" "..tostring(deprecated_global_options)) + ok = false; + end + for host, options in enabled_hosts() do local host_options = set.new(it.to_array(it.keys(options))); local misplaced_options = set.intersection(host_options, known_global_options); for name in pairs(options) do @@ -821,8 +871,9 @@ function commands.check(arg) print(" You need to move the following option"..(n>1 and "s" or "")..": "..table.concat(it.to_array(misplaced_options), ", ")); end local subdomain = host:match("^[^.]+"); - if not(is_component) and (subdomain == "jabber" or subdomain == "xmpp" + if not(host_options:contains("component_module")) and (subdomain == "jabber" or subdomain == "xmpp" or subdomain == "chat" or subdomain == "im") then + print(""); print(" Suggestion: If "..host.. " is a new host with no real users yet, consider renaming it now to"); print(" "..host:gsub("^[^.]+%.", "")..". You can use SRV records to redirect XMPP clients and servers to "..host.."."); print(" For more information see: http://prosody.im/doc/dns"); @@ -833,6 +884,7 @@ function commands.check(arg) end if not what or what == "dns" then local dns = require "net.dns"; + local idna = require "util.encodings".idna; local ip = require "util.ip"; local c2s_ports = set.new(config.get("*", "c2s_ports") or {5222}); local s2s_ports = set.new(config.get("*", "s2s_ports") or {5269}); @@ -851,13 +903,13 @@ function commands.check(arg) local fqdn = socket.dns.tohostname(socket.dns.gethostname()); if fqdn then - local res = dns.lookup(fqdn, "A"); + local res = dns.lookup(idna.to_ascii(fqdn), "A"); if res then for _, record in ipairs(res) do external_addresses:add(record.a); end end - local res = dns.lookup(fqdn, "AAAA"); + local res = dns.lookup(idna.to_ascii(fqdn), "AAAA"); if res then for _, record in ipairs(res) do external_addresses:add(record.aaaa); @@ -865,7 +917,7 @@ function commands.check(arg) end end - local local_addresses = socket.local_addresses and socket.local_addresses() or {}; + local local_addresses = require"util.net".local_addresses() or {}; for addr in it.values(local_addresses) do if not ip.new_ip(addr).private then @@ -883,14 +935,14 @@ function commands.check(arg) local v6_supported = not not socket.tcp6; - for host, host_options in it.filter("*", pairs(config.getconfig())) do + for host, host_options in enabled_hosts() do local all_targets_ok, some_targets_ok = true, false; local is_component = not not host_options.component_module; print("Checking DNS for "..(is_component and "component" or "host").." "..host.."..."); local target_hosts = set.new(); if not is_component then - local res = dns.lookup("_xmpp-client._tcp."..host..".", "SRV"); + local res = dns.lookup("_xmpp-client._tcp."..idna.to_ascii(host)..".", "SRV"); if res then for _, record in ipairs(res) do target_hosts:add(record.srv.target); @@ -907,7 +959,7 @@ function commands.check(arg) end end end - local res = dns.lookup("_xmpp-server._tcp."..host..".", "SRV"); + local res = dns.lookup("_xmpp-server._tcp."..idna.to_ascii(host)..".", "SRV"); if res then for _, record in ipairs(res) do target_hosts:add(record.srv.target); @@ -932,9 +984,28 @@ function commands.check(arg) target_hosts:remove("localhost"); end + local modules = set.new(it.to_array(it.values(host_options.modules_enabled))) + + set.new(it.to_array(it.values(config.get("*", "modules_enabled")))) + + set.new({ config.get(host, "component_module") }); + + if modules:contains("proxy65") then + local proxy65_target = config.get(host, "proxy65_address") or host; + local A, AAAA = dns.lookup(idna.to_ascii(proxy65_target), "A"), dns.lookup(idna.to_ascii(proxy65_target), "AAAA"); + local prob = {}; + if not A then + table.insert(prob, "A"); + end + if v6_supported and not AAAA then + table.insert(prob, "AAAA"); + end + if #prob > 0 then + print(" File transfer proxy "..proxy65_target.." has no "..table.concat(prob, "/").." record. Create one or set 'proxy65_address' to the correct host/IP."); + end + end + for host in target_hosts do local host_ok_v4, host_ok_v6; - local res = dns.lookup(host, "A"); + local res = dns.lookup(idna.to_ascii(host), "A"); if res then for _, record in ipairs(res) do if external_addresses:contains(record.a) then @@ -950,7 +1021,7 @@ function commands.check(arg) end end end - local res = dns.lookup(host, "AAAA"); + local res = dns.lookup(idna.to_ascii(host), "AAAA"); if res then for _, record in ipairs(res) do if external_addresses:contains(record.aaaa) then @@ -998,6 +1069,79 @@ function commands.check(arg) ok = false; end end + if not what or what == "certs" then + local cert_ok; + print"Checking certificates..." + local x509_verify_identity = require"util.x509".verify_identity; + local ssl = dependencies.softreq"ssl"; + -- local datetime_parse = require"util.datetime".parse_x509; + local load_cert = ssl and ssl.x509 and ssl.x509.load; + -- or ssl.cert_from_pem + if not ssl then + print("LuaSec not available, can't perform certificate checks") + if what == "certs" then cert_ok = false end + elseif not load_cert then + print("This version of LuaSec (" .. ssl._VERSION .. ") does not support certificate checking"); + cert_ok = false + else + for host in enabled_hosts() do + print("Checking certificate for "..host); + -- First, let's find out what certificate this host uses. + local ssl_config = config.rawget(host, "ssl"); + if not ssl_config then + local base_host = host:match("%.(.*)"); + ssl_config = config.get(base_host, "ssl"); + end + if not ssl_config then + print(" No 'ssl' option defined for "..host) + cert_ok = false + elseif not ssl_config.certificate then + print(" No 'certificate' set in ssl option for "..host) + cert_ok = false + elseif not ssl_config.key then + print(" No 'key' set in ssl option for "..host) + cert_ok = false + else + local key, err = io.open(ssl_config.key); -- Permissions check only + if not key then + print(" Could not open "..ssl_config.key..": "..err); + cert_ok = false + else + key:close(); + end + local cert_fh, err = io.open(ssl_config.certificate); -- Load the file. + if not cert_fh then + print(" Could not open "..ssl_config.certificate..": "..err); + cert_ok = false + else + print(" Certificate: "..ssl_config.certificate) + local cert = load_cert(cert_fh:read"*a"); cert_fh = cert_fh:close(); + if not cert:validat(os.time()) then + print(" Certificate has expired.") + cert_ok = false + end + if config.get(host, "component_module") == nil + and not x509_verify_identity(host, "_xmpp-client", cert) then + print(" Not vaild for client connections to "..host..".") + cert_ok = false + end + if (not (config.get(host, "anonymous_login") + or config.get(host, "authentication") == "anonymous")) + and not x509_verify_identity(host, "_xmpp-client", cert) then + print(" Not vaild for server-to-server connections to "..host..".") + cert_ok = false + end + end + end + end + if cert_ok == false then + print("") + print("For more information about certificates please see http://prosody.im/doc/certificates"); + ok = false + end + end + print("") + end if not ok then print("Problems found, see above."); else