X-Git-Url: https://git.enpas.org/?a=blobdiff_plain;f=prosodyctl;h=226ff2cf48b370ea991b3a63228191074243c5bf;hb=61a7db53da184889b344fbbee54760bc2e626bb9;hp=df8c8e755312df4b20ba4869cdba6a1a8a0aadbe;hpb=289498a056350129cacfe32d84feebcf1f61f816;p=prosody.git diff --git a/prosodyctl b/prosodyctl index df8c8e75..226ff2cf 100755 --- a/prosodyctl +++ b/prosodyctl @@ -11,10 +11,10 @@ -- Will be modified by configure script if run -- -CFG_SOURCEDIR=os.getenv("PROSODY_SRCDIR"); -CFG_CONFIGDIR=os.getenv("PROSODY_CFGDIR"); -CFG_PLUGINDIR=os.getenv("PROSODY_PLUGINDIR"); -CFG_DATADIR=os.getenv("PROSODY_DATADIR"); +CFG_SOURCEDIR=CFG_SOURCEDIR or os.getenv("PROSODY_SRCDIR"); +CFG_CONFIGDIR=CFG_CONFIGDIR or os.getenv("PROSODY_CFGDIR"); +CFG_PLUGINDIR=CFG_PLUGINDIR or os.getenv("PROSODY_PLUGINDIR"); +CFG_DATADIR=CFG_DATADIR or os.getenv("PROSODY_DATADIR"); -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- @@ -233,6 +233,7 @@ local function make_host(hostname) type = "local", events = prosody.events, modules = {}, + sessions = {}, users = require "core.usermanager".new_null_provider(hostname) }; end @@ -244,13 +245,14 @@ end local modulemanager = require "core.modulemanager" local prosodyctl = require "util.prosodyctl" -require "socket" +local socket = require "socket" ----------------------- -- FIXME: Duplicate code waiting for util.startup function read_version() -- Try to determine version local version_file = io.open((CFG_SOURCEDIR or ".").."/prosody.version"); + prosody.version = "unknown"; if version_file then prosody.version = version_file:read("*a"):gsub("%s*$", ""); version_file:close(); @@ -258,7 +260,9 @@ function read_version() prosody.version = "hg:"..prosody.version; end else - prosody.version = "unknown"; + local hg = require"util.mercurial"; + local hgid = hg.check_id(CFG_SOURCEDIR or "."); + if hgid then prosody.version = "hg:" .. hgid; end end end @@ -528,16 +532,32 @@ function commands.about(arg) return 1; end + local pwd = "."; + local lfs = require "lfs"; local array = require "util.array"; local keys = require "util.iterators".keys; + local hg = require"util.mercurial"; + local relpath = config.resolve_relative_path; print("Prosody "..(prosody.version or "(unknown version)")); print(""); print("# Prosody directories"); - print("Data directory: ", CFG_DATADIR or "./"); - print("Plugin directory:", CFG_PLUGINDIR or "./"); - print("Config directory:", CFG_CONFIGDIR or "./"); - print("Source directory:", CFG_SOURCEDIR or "./"); + print("Data directory: "..relpath(pwd, data_path)); + print("Config directory: "..relpath(pwd, CFG_CONFIGDIR or ".")); + print("Source directory: "..relpath(pwd, CFG_SOURCEDIR or ".")); + print("Plugin directories:") + print(" "..(prosody.paths.plugins:gsub("([^;]+);?", function(path) + local opath = path; + path = config.resolve_relative_path(pwd, path); + local hgid, hgrepo = hg.check_id(path); + if not hgid and hgrepo then + return path.." - "..hgrepo .."!\n "; + end + -- 010452cfaf53 is the first commit in the prosody-modules repository + hgrepo = hgrepo == "010452cfaf53" and "prosody-modules"; + return path..(hgid and " - "..(hgrepo or "HG").." rev: "..hgid or "") + .."\n "; + end))); print(""); print("# Lua environment"); print("Lua version: ", _G._VERSION); @@ -552,13 +572,15 @@ function commands.about(arg) print(" "..path); end print(""); - local luarocks_status = (pcall(require, "luarocks.loader") and "Installed ("..(luarocks.cfg.program_version or "2.x+")..")") + local luarocks_status = (pcall(require, "luarocks.loader") and "Installed ("..(package.loaded["luarocks.cfg"].program_version or "2.x+")..")") or (pcall(require, "luarocks.require") and "Installed (1.x)") or "Not installed"; print("LuaRocks: ", luarocks_status); print(""); print("# Lua module versions"); local module_versions, longest_name = {}, 8; + local luaevent =dependencies.softreq"luaevent"; + local ssl = dependencies.softreq"ssl"; for name, module in pairs(package.loaded) do if type(module) == "table" and rawget(module, "_VERSION") and name ~= "_G" and not name:match("%.") then @@ -568,8 +590,11 @@ function commands.about(arg) module_versions[name] = module._VERSION; end end + if luaevent then + module_versions["libevent"] = luaevent.core.libevent_version(); + end local sorted_keys = array.collect(keys(module_versions)):sort(); - for _, name in ipairs(array.collect(keys(module_versions)):sort()) do + for _, name in ipairs(sorted_keys) do print(name..":"..string.rep(" ", longest_name-#name), module_versions[name]); end print(""); @@ -654,40 +679,65 @@ local lfs; local cert_commands = {}; -local function ask_overwrite(filename) - return lfs.attributes(filename) and not show_yesno("Overwrite "..filename .. "?"); +-- If a file already exists, ask if the user wants to use it or replace it +-- Backups the old file if replaced +local function use_existing(filename) + local attrs = lfs.attributes(filename); + if attrs then + if show_yesno(filename .. " exists, do you want to replace it? [y/n]") then + local backup = filename..".bkp~"..os.date("%FT%T", attrs.change); + os.rename(filename, backup); + show_message(filename.." backed up to "..backup); + else + -- Use the existing file + return true; + end + end end function cert_commands.config(arg) if #arg >= 1 and arg[1] ~= "--help" then local conf_filename = (CFG_DATADIR or "./certs") .. "/" .. arg[1] .. ".cnf"; - if ask_overwrite(conf_filename) then + if use_existing(conf_filename) then return nil, conf_filename; end + local distinguished_name; + if arg[#arg]:find("^/") then + distinguished_name = table.remove(arg); + end local conf = openssl.config.new(); conf:from_prosody(hosts, config, arg); - show_message("Please provide details to include in the certificate config file."); - show_message("Leave the field empty to use the default value or '.' to exclude the field.") - for i, k in ipairs(openssl._DN_order) do - local v = conf.distinguished_name[k]; - if v then - local nv; - if k == "commonName" then - v = arg[1] - elseif k == "emailAddress" then - v = "xmpp@" .. arg[1]; - elseif k == "countryName" then - local tld = arg[1]:match"%.([a-z]+)$"; - if tld and #tld == 2 and tld ~= "uk" then - v = tld:upper(); + if distinguished_name then + local dn = {}; + for k, v in distinguished_name:gmatch("/([^=/]+)=([^/]+)") do + table.insert(dn, k); + dn[k] = v; + end + conf.distinguished_name = dn; + else + show_message("Please provide details to include in the certificate config file."); + show_message("Leave the field empty to use the default value or '.' to exclude the field.") + for i, k in ipairs(openssl._DN_order) do + local v = conf.distinguished_name[k]; + if v then + local nv; + if k == "commonName" then + v = arg[1] + elseif k == "emailAddress" then + v = "xmpp@" .. arg[1]; + elseif k == "countryName" then + local tld = arg[1]:match"%.([a-z]+)$"; + if tld and #tld == 2 and tld ~= "uk" then + v = tld:upper(); + end end + nv = show_prompt(("%s (%s):"):format(k, nv or v)); + nv = (not nv or nv == "") and v or nv; + if nv:find"[\192-\252][\128-\191]+" then + conf.req.string_mask = "utf8only" + end + conf.distinguished_name[k] = nv ~= "." and nv or nil; end - nv = show_prompt(("%s (%s):"):format(k, nv or v)); - nv = (not nv or nv == "") and v or nv; - if nv:find"[\192-\252][\128-\191]+" then - conf.req.string_mask = "utf8only" - end - conf.distinguished_name[k] = nv ~= "." and nv or nil; end end local conf_file, err = io.open(conf_filename, "w"); @@ -709,7 +759,7 @@ end function cert_commands.key(arg) if #arg >= 1 and arg[1] ~= "--help" then local key_filename = (CFG_DATADIR or "./certs") .. "/" .. arg[1] .. ".key"; - if ask_overwrite(key_filename) then + if use_existing(key_filename) then return nil, key_filename; end os.remove(key_filename); -- This file, if it exists is unlikely to have write permissions @@ -731,7 +781,7 @@ end function cert_commands.request(arg) if #arg >= 1 and arg[1] ~= "--help" then local req_filename = (CFG_DATADIR or "./certs") .. "/" .. arg[1] .. ".req"; - if ask_overwrite(req_filename) then + if use_existing(req_filename) then return nil, req_filename; end local _, key_filename = cert_commands.key({arg[1]}); @@ -749,7 +799,7 @@ end function cert_commands.generate(arg) if #arg >= 1 and arg[1] ~= "--help" then local cert_filename = (CFG_DATADIR or "./certs") .. "/" .. arg[1] .. ".crt"; - if ask_overwrite(cert_filename) then + if use_existing(cert_filename) then return nil, cert_filename; end local _, key_filename = cert_commands.key({arg[1]}); @@ -759,6 +809,8 @@ function cert_commands.generate(arg) and openssl.req{new=true, x509=true, nodes=true, key=key_filename, days=365, sha256=true, utf8=true, config=conf_filename, out=cert_filename} then show_message("Certificate written to ".. cert_filename); + print(); + show_message(("Example config:\n\nssl = {\n\tcertificate = %q;\n\tkey = %q;\n}"):format(cert_filename, key_filename)); else show_message("There was a problem, see OpenSSL output"); end @@ -816,11 +868,13 @@ function commands.check(arg) if not what or what == "config" then print("Checking config..."); local deprecated = set.new({ - "bosh_ports", "disallow_s2s", "no_daemonize", "anonymous_login", + "bosh_ports", "disallow_s2s", "no_daemonize", "anonymous_login", "require_encryption", + "vcard_compatibility", }); local known_global_options = set.new({ "pidfile", "log", "plugin_paths", "prosody_user", "prosody_group", "daemonize", - "umask", "prosodyctl_timeout", "use_ipv6", "use_libevent", "network_settings" + "umask", "prosodyctl_timeout", "use_ipv6", "use_libevent", "network_settings", + "network_backend", "http_default_host", }); local config = config.getconfig(); -- Check that we have any global options (caused by putting a host at the top) @@ -841,6 +895,20 @@ function commands.check(arg) print(" All hosts are disabled. Remove enabled = false from at least one VirtualHost section") end end + if not config["*"].modules_enabled then + print(" No global modules_enabled is set?"); + local suggested_global_modules; + for host, options in enabled_hosts() do + if not options.component_module and options.modules_enabled then + suggested_global_modules = set.intersection(suggested_global_modules or set.new(options.modules_enabled), set.new(options.modules_enabled)); + end + end + if not suggested_global_modules:empty() then + print(" Consider moving these modules into modules_enabled in the global section:") + print(" "..tostring(suggested_global_modules / function (x) return ("%q"):format(x) end)); + end + print(); + end -- Check for global options under hosts local global_options = set.new(it.to_array(it.keys(config["*"]))); local deprecated_global_options = set.intersection(global_options, deprecated); @@ -856,7 +924,7 @@ function commands.check(arg) for name in pairs(options) do if name:match("^interfaces?") or name:match("_ports?$") or name:match("_interfaces?$") - or name:match("_ssl$") then + or (name:match("_ssl$") and not name:match("^[cs]2s_ssl$")) then misplaced_options:add(name); end end @@ -879,6 +947,72 @@ function commands.check(arg) print(" For more information see: http://prosody.im/doc/dns"); end end + local all_modules = set.new(config["*"].modules_enabled); + local all_options = set.new(it.to_array(it.keys(config["*"]))); + for host in enabled_hosts() do + all_options:include(set.new(it.to_array(it.keys(config[host])))); + all_modules:include(set.new(config[host].modules_enabled)); + end + for mod in all_modules do + if mod:match("^mod_") then + print(""); + print(" Modules in modules_enabled should not have the 'mod_' prefix included."); + print(" Change '"..mod.."' to '"..mod:match("^mod_(.*)").."'."); + elseif mod:match("^auth_") then + print(""); + print(" Authentication modules should not be added to modules_enabled,"); + print(" but be specified in the 'authentication' option."); + print(" Remove '"..mod.."' from modules_enabled and instead add"); + print(" authentication = '"..mod:match("^auth_(.*)").."'"); + print(" For more information see https://prosody.im/doc/authentication"); + elseif mod:match("^storage_") then + print(""); + print(" storage modules should not be added to modules_enabled,"); + print(" but be specified in the 'storage' option."); + print(" Remove '"..mod.."' from modules_enabled and instead add"); + print(" storage = '"..mod:match("^storage_(.*)").."'"); + print(" For more information see https://prosody.im/doc/storage"); + end + end + local require_encryption = set.intersection(all_options, set.new({"require_encryption", "c2s_require_encryption", "s2s_require_encryption"})):empty(); + local ssl = dependencies.softreq"ssl"; + if not ssl then + if not require_encryption then + print(""); + print(" You require encryption but LuaSec is not available."); + print(" Connections will fail."); + ok = false; + end + elseif not ssl.loadcertificate then + if all_options:contains("s2s_secure_auth") then + print(""); + print(" You have set s2s_secure_auth but your version of LuaSec does "); + print(" not support certificate validation, so all s2s connections will"); + print(" fail."); + ok = false; + elseif all_options:contains("s2s_secure_domains") then + local secure_domains = set.new(); + for host in enabled_hosts() do + if config[host].s2s_secure_auth == true then + secure_domains:add("*"); + else + secure_domains:include(set.new(config[host].s2s_secure_domains)); + end + end + if not secure_domains:empty() then + print(""); + print(" You have set s2s_secure_domains but your version of LuaSec does "); + print(" not support certificate validation, so s2s connections to/from "); + print(" these domains will fail."); + ok = false; + end + end + elseif require_encryption and not all_modules:contains("tls") then + print(""); + print(" You require encryption but mod_tls is not enabled."); + print(" Connections will fail."); + ok = false; + end print("Done.\n"); end @@ -935,11 +1069,15 @@ function commands.check(arg) local v6_supported = not not socket.tcp6; - for host, host_options in enabled_hosts() do + for jid, host_options in enabled_hosts() do local all_targets_ok, some_targets_ok = true, false; + local node, host = jid_split(jid); local is_component = not not host_options.component_module; - print("Checking DNS for "..(is_component and "component" or "host").." "..host.."..."); + print("Checking DNS for "..(is_component and "component" or "host").." "..jid.."..."); + if node then + print("Only the domain part ("..host..") is used in DNS.") + end local target_hosts = set.new(); if not is_component then local res = dns.lookup("_xmpp-client._tcp."..idna.to_ascii(host)..".", "SRV"); @@ -953,7 +1091,7 @@ function commands.check(arg) else if c2s_srv_required then print(" No _xmpp-client SRV record found for "..host..", but it looks like you need one."); - all_targst_ok = false; + all_targets_ok = false; else target_hosts:add(host); end @@ -984,8 +1122,8 @@ 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")))) + local modules = set.new(it.to_array(it.values(host_options.modules_enabled or {}))) + + set.new(it.to_array(it.values(config.get("*", "modules_enabled") or {}))) + set.new({ config.get(host, "component_module") }); if modules:contains("proxy65") then @@ -1073,9 +1211,10 @@ function commands.check(arg) local cert_ok; print"Checking certificates..." local x509_verify_identity = require"util.x509".verify_identity; + local create_context = require "core.certmanager".create_context; local ssl = dependencies.softreq"ssl"; -- local datetime_parse = require"util.datetime".parse_x509; - local load_cert = ssl and ssl.x509 and ssl.x509.load; + local load_cert = ssl and ssl.loadcertificate; -- or ssl.cert_from_pem if not ssl then print("LuaSec not available, can't perform certificate checks") @@ -1087,19 +1226,18 @@ function commands.check(arg) 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) + local host_ssl_config = config.rawget(host, "ssl") + or config.rawget(host:match("%.(.*)"), "ssl"); + local global_ssl_config = config.rawget("*", "ssl"); + local ok, err, ssl_config = create_context(host, "server", host_ssl_config, global_ssl_config); + if not ok then + print(" Error: "..err); cert_ok = false elseif not ssl_config.certificate then - print(" No 'certificate' set in ssl option for "..host) + print(" No 'certificate' found for "..host) cert_ok = false elseif not ssl_config.key then - print(" No 'key' set in ssl option for "..host) + print(" No 'key' found for "..host) cert_ok = false else local key, err = io.open(ssl_config.key); -- Permissions check only @@ -1119,16 +1257,23 @@ function commands.check(arg) if not cert:validat(os.time()) then print(" Certificate has expired.") cert_ok = false + elseif not cert:validat(os.time() + 86400) then + print(" Certificate expires within one day.") + cert_ok = false + elseif not cert:validat(os.time() + 86400*7) then + print(" Certificate expires within one week.") + elseif not cert:validat(os.time() + 86400*31) then + print(" Certificate expires within one month.") 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..".") + print(" Not valid 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..".") + and not x509_verify_identity(host, "_xmpp-server", cert) then + print(" Not valid for server-to-server connections to "..host..".") cert_ok = false end end