aboutsummaryrefslogtreecommitdiffstats
path: root/util/prosodyctl
diff options
context:
space:
mode:
Diffstat (limited to 'util/prosodyctl')
-rw-r--r--util/prosodyctl/cert.lua16
-rw-r--r--util/prosodyctl/check.lua140
-rw-r--r--util/prosodyctl/shell.lua49
3 files changed, 132 insertions, 73 deletions
diff --git a/util/prosodyctl/cert.lua b/util/prosodyctl/cert.lua
index 02c81585..aea61c20 100644
--- a/util/prosodyctl/cert.lua
+++ b/util/prosodyctl/cert.lua
@@ -1,8 +1,8 @@
local lfs = require "lfs";
-local pctl = require "util.prosodyctl";
-local hi = require "util.human.io";
-local configmanager = require "core.configmanager";
+local pctl = require "prosody.util.prosodyctl";
+local hi = require "prosody.util.human.io";
+local configmanager = require "prosody.core.configmanager";
local openssl;
@@ -24,7 +24,7 @@ local function use_existing(filename)
end
end
-local have_pposix, pposix = pcall(require, "util.pposix");
+local have_pposix, pposix = pcall(require, "prosody.util.pposix");
local cert_basedir = prosody.paths.data == "." and "./certs" or prosody.paths.data;
if have_pposix and pposix.getuid() == 0 then
-- FIXME should be enough to check if this directory is writable
@@ -179,7 +179,7 @@ local function copy(from, to, umask, owner, group)
os.execute(("chown -c --reference=%s %s"):format(sh_esc(cert_basedir), sh_esc(to)));
elseif owner and group then
local ok = os.execute(("chown %s:%s %s"):format(sh_esc(owner), sh_esc(group), sh_esc(to)));
- assert(ok == true or ok == 0, "Failed to change ownership of "..to);
+ assert(ok, "Failed to change ownership of "..to);
end
if old_umask then pposix.umask(old_umask); end
return true;
@@ -219,7 +219,7 @@ function cert_commands.import(arg)
owner = configmanager.get("*", "prosody_user") or "prosody";
group = configmanager.get("*", "prosody_group") or owner;
end
- local cm = require "core.certmanager";
+ local cm = require "prosody.core.certmanager";
local files_by_name = {}
for _, dir in ipairs(arg) do
cm.index_certs(dir, files_by_name);
@@ -271,7 +271,7 @@ end
local function cert(arg)
if #arg >= 1 and arg[1] ~= "--help" then
- openssl = require "util.openssl";
+ openssl = require "prosody.util.openssl";
lfs = require "lfs";
local cert_dir_attrs = lfs.attributes(cert_basedir);
if not cert_dir_attrs then
@@ -303,7 +303,7 @@ local function cert(arg)
end
return cert_commands[subcmd](arg);
elseif subcmd == "check" then
- return require "util.prosodyctl.check".check({"certs"});
+ return require "prosody.util.prosodyctl.check".check({"certs"});
end
end
pctl.show_usage("cert config|request|generate|key|import", "Helpers for generating X.509 certificates and keys.")
diff --git a/util/prosodyctl/check.lua b/util/prosodyctl/check.lua
index 5de8e3a6..5e7087c5 100644
--- a/util/prosodyctl/check.lua
+++ b/util/prosodyctl/check.lua
@@ -1,24 +1,24 @@
-local configmanager = require "core.configmanager";
-local moduleapi = require "core.moduleapi";
-local show_usage = require "util.prosodyctl".show_usage;
-local show_warning = require "util.prosodyctl".show_warning;
-local is_prosody_running = require "util.prosodyctl".isrunning;
-local parse_args = require "util.argparse".parse;
-local dependencies = require "util.dependencies";
+local configmanager = require "prosody.core.configmanager";
+local moduleapi = require "prosody.core.moduleapi";
+local show_usage = require "prosody.util.prosodyctl".show_usage;
+local show_warning = require "prosody.util.prosodyctl".show_warning;
+local is_prosody_running = require "prosody.util.prosodyctl".isrunning;
+local parse_args = require "prosody.util.argparse".parse;
+local dependencies = require "prosody.util.dependencies";
local socket = require "socket";
local socket_url = require "socket.url";
-local jid_split = require "util.jid".prepped_split;
-local modulemanager = require "core.modulemanager";
-local async = require "util.async";
-local httputil = require "util.http";
+local jid_split = require "prosody.util.jid".prepped_split;
+local modulemanager = require "prosody.core.modulemanager";
+local async = require "prosody.util.async";
+local httputil = require "prosody.util.http";
local function api(host)
return setmetatable({ name = "prosodyctl.check"; host = host; log = prosody.log }, { __index = moduleapi })
end
local function check_ojn(check_type, target_host)
- local http = require "net.http"; -- .new({});
- local json = require "util.json";
+ local http = require "prosody.net.http"; -- .new({});
+ local json = require "prosody.util.json";
local response, err = async.wait_for(http.request(
("https://observe.jabber.network/api/v1/check/%s"):format(httputil.urlencode(check_type)),
@@ -46,7 +46,7 @@ local function check_ojn(check_type, target_host)
end
local function check_probe(base_url, probe_module, target)
- local http = require "net.http"; -- .new({});
+ local http = require "prosody.net.http"; -- .new({});
local params = httputil.formencode({ module = probe_module; target = target })
local response, err = async.wait_for(http.request(base_url .. "?" .. params));
@@ -67,8 +67,8 @@ local function check_probe(base_url, probe_module, target)
end
local function check_turn_service(turn_service, ping_service)
- local ip = require "util.ip";
- local stun = require "net.stun";
+ local ip = require "prosody.util.ip";
+ local stun = require "prosody.net.stun";
-- Create UDP socket for communication with the server
local sock = assert(require "socket".udp());
@@ -160,7 +160,7 @@ local function check_turn_service(turn_service, ping_service)
result.error = "TURN server did not response to allocation request: "..err;
return result;
elseif alloc_response:is_err_resp() then
- result.error = ("TURN allocation failed: %d (%s)"):format(alloc_response:get_error());
+ result.error = ("TURN server failed to create allocation: %d (%s)"):format(alloc_response:get_error());
return result;
elseif not alloc_response:is_success_resp() then
result.error = ("Unexpected TURN response: %d (%s)"):format(alloc_response:get_type());
@@ -309,18 +309,15 @@ local function check(arg)
print("Error: Unknown parameter: "..opts_info);
return 1;
end
- local array = require "util.array";
- local set = require "util.set";
- local it = require "util.iterators";
+ local array = require "prosody.util.array";
+ local set = require "prosody.util.set";
+ local it = require "prosody.util.iterators";
local ok = true;
+ local function contains_match(hayset, needle) for member in hayset do if member:find(needle) then return true end end end
local function disabled_hosts(host, conf) return host ~= "*" and conf.enabled ~= false; end
local function enabled_hosts() return it.filter(disabled_hosts, pairs(configmanager.getconfig())); end
- if not (what == nil or what == "disabled" or what == "config" or what == "dns" or what == "certs" or what == "connectivity" or what == "turn") then
- show_warning("Don't know how to check '%s'. Try one of 'config', 'dns', 'certs', 'disabled', 'turn' or 'connectivity'.", what);
- show_warning("Note: The connectivity check will connect to a remote server.");
- return 1;
- end
- if not what or what == "disabled" then
+ local checks = {};
+ function checks.disabled()
local disabled_hosts_set = set.new();
for host in it.filter("*", pairs(configmanager.getconfig())) do
if api(host):get_option_boolean("enabled") == false then
@@ -335,7 +332,7 @@ local function check(arg)
print""
end
end
- if not what or what == "config" then
+ function checks.config()
print("Checking config...");
if what == "config" then
@@ -510,9 +507,9 @@ local function check(arg)
end
for k, v in pairs(modules) do
if type(k) ~= "number" or type(v) ~= "string" then
- print(" The " .. name .. " in the " .. host .. " section should not be a map of " .. type(k) .. " to " .. type(v)
- .. " but a list of strings, e.g.");
+ print(" The " .. name .. " in the " .. host .. " section should be a list of strings, e.g.");
print(" " .. name .. " = { \"name_of_module\", \"another_plugin\", }")
+ print(" It should not contain key = value pairs, try putting them outside the {} brackets.");
ok = false
break
end
@@ -740,14 +737,14 @@ local function check(arg)
print("Done.\n");
end
- if not what or what == "dns" then
- local dns = require "net.dns";
+ function checks.dns()
+ local dns = require "prosody.net.dns";
pcall(function ()
- local unbound = require"net.unbound";
+ local unbound = require"prosody.net.unbound";
dns = unbound.dns;
end)
- local idna = require "util.encodings".idna;
- local ip = require "util.ip";
+ local idna = require "prosody.util.encodings".idna;
+ local ip = require "prosody.util.ip";
local global = api("*");
local c2s_ports = global:get_option_set("c2s_ports", {5222});
local s2s_ports = global:get_option_set("s2s_ports", {5269});
@@ -810,7 +807,7 @@ local function check(arg)
end
end
- local local_addresses = require"util.net".local_addresses() or {};
+ local local_addresses = require"prosody.util.net".local_addresses() or {};
for addr in it.values(local_addresses) do
if not ip.new_ip(addr).private then
@@ -977,9 +974,6 @@ local function check(arg)
end
local known_http_modules = set.new { "bosh"; "http_files"; "http_file_share"; "http_openmetrics"; "websocket" };
- local function contains_match(hayset, needle)
- for member in hayset do if member:find(needle) then return true end end
- end
if modules:contains("http") or not set.intersection(modules, known_http_modules):empty()
or contains_match(modules, "^http_") or contains_match(modules, "_web$") then
@@ -1115,11 +1109,14 @@ local function check(arg)
ok = false;
end
end
- if not what or what == "certs" then
+ function checks.certs()
local cert_ok;
print"Checking certificates..."
- local x509_verify_identity = require"util.x509".verify_identity;
- local create_context = require "core.certmanager".create_context;
+ local x509_verify_identity = require"prosody.util.x509".verify_identity;
+ local use_dane = configmanager.get("*", "use_dane");
+ local pem2der = require"prosody.util.x509".pem2der;
+ local sha256 = require"prosody.util.hashes".sha256;
+ local create_context = require "prosody.core.certmanager".create_context;
local ssl = dependencies.softreq"ssl";
-- local datetime_parse = require"util.datetime".parse_x509;
local load_cert = ssl and ssl.loadcertificate;
@@ -1132,13 +1129,14 @@ local function check(arg)
cert_ok = false
else
for host in it.filter(skip_bare_jid_hosts, enabled_hosts()) do
+ local modules = modulemanager.get_modules_for_host(host);
print("Checking certificate for "..host);
-- First, let's find out what certificate this host uses.
local host_ssl_config = configmanager.rawget(host, "ssl")
or configmanager.rawget(host:match("%.(.*)"), "ssl");
local global_ssl_config = configmanager.rawget("*", "ssl");
- local ok, err, ssl_config = create_context(host, "server", host_ssl_config, global_ssl_config);
- if not ok then
+ local ctx_ok, err, ssl_config = create_context(host, "server", host_ssl_config, global_ssl_config);
+ if not ctx_ok then
print(" Error: "..err);
cert_ok = false
elseif not ssl_config.certificate then
@@ -1173,17 +1171,39 @@ local function check(arg)
elseif not cert:validat(os.time() + 86400*31) then
print(" Certificate expires within one month.")
end
- if select(2, modulemanager.get_modules_for_host(host)) == nil
- and not x509_verify_identity(host, "_xmpp-client", cert) then
+ if modules:contains("c2s") and not x509_verify_identity(host, "_xmpp-client", cert) then
print(" Not valid for client connections to "..host..".")
cert_ok = false
end
- if (not (api(host):get_option_boolean("anonymous_login", false)
- or api(host):get_option_string("authentication", "internal_hashed") == "anonymous"))
- and not x509_verify_identity(host, "_xmpp-server", cert) then
+ local anon = api(host):get_option_string("authentication", "internal_hashed") == "anonymous";
+ local anon_s2s = api(host):get_option_boolean("allow_anonymous_s2s", false);
+ if modules:contains("s2s") and (anon_s2s or not anon) and not x509_verify_identity(host, "_xmpp-server", cert) then
print(" Not valid for server-to-server connections to "..host..".")
cert_ok = false
end
+
+ local known_http_modules = set.new { "bosh"; "http_files"; "http_file_share"; "http_openmetrics"; "websocket" };
+ local http_loaded = modules:contains("http")
+ or not set.intersection(modules, known_http_modules):empty()
+ or contains_match(modules, "^http_")
+ or contains_match(modules, "_web$");
+
+ local http_host = api(host):get_option_string("http_host", host);
+ if api(host):get_option_string("http_external_url") then
+ -- Assumed behind a reverse proxy
+ http_loaded = false;
+ end
+ if http_loaded and not x509_verify_identity(http_host, nil, cert) then
+ print(" Not valid for HTTPS connections to "..host..".")
+ cert_ok = false
+ end
+ if use_dane then
+ if cert.pubkey then
+ print(" DANE: TLSA 3 1 1 "..sha256(pem2der(cert:pubkey()), true))
+ elseif cert.pem then
+ print(" DANE: TLSA 3 0 1 "..sha256(pem2der(cert:pem()), true))
+ end
+ end
end
end
end
@@ -1196,7 +1216,7 @@ local function check(arg)
print("")
end
-- intentionally not doing this by default
- if what == "connectivity" then
+ function checks.connectivity()
local _, prosody_is_running = is_prosody_running();
if api("*"):get_option_string("pidfile") and not prosody_is_running then
print("Prosody does not appear to be running, which is required for this test.");
@@ -1288,7 +1308,7 @@ local function check(arg)
print("Note: It does not ensure that the check actually reaches this specific prosody instance.")
end
- if not what or what == "turn" then
+ function checks.turn()
local turn_enabled_hosts = {};
local turn_services = {};
@@ -1363,6 +1383,26 @@ local function check(arg)
end
end
end
+ if what == nil or what == "all" then
+ local ret;
+ ret = checks.disabled();
+ if ret ~= nil then return ret; end
+ ret = checks.config();
+ if ret ~= nil then return ret; end
+ ret = checks.dns();
+ if ret ~= nil then return ret; end
+ ret = checks.certs();
+ if ret ~= nil then return ret; end
+ ret = checks.turn();
+ if ret ~= nil then return ret; end
+ elseif checks[what] then
+ local ret = checks[what]();
+ if ret ~= nil then return ret; end
+ else
+ show_warning("Don't know how to check '%s'. Try one of 'config', 'dns', 'certs', 'disabled', 'turn' or 'connectivity'.", what);
+ show_warning("Note: The connectivity check will connect to a remote server.");
+ return 1;
+ end
if not ok then
print("Problems found, see above.");
diff --git a/util/prosodyctl/shell.lua b/util/prosodyctl/shell.lua
index 8cf7df69..f3279e75 100644
--- a/util/prosodyctl/shell.lua
+++ b/util/prosodyctl/shell.lua
@@ -1,13 +1,15 @@
-local config = require "core.configmanager";
-local server = require "net.server";
-local st = require "util.stanza";
-local path = require "util.paths";
-local parse_args = require "util.argparse".parse;
-local unpack = table.unpack or _G.unpack;
+local config = require "prosody.core.configmanager";
+local server = require "prosody.net.server";
+local st = require "prosody.util.stanza";
+local path = require "prosody.util.paths";
+local parse_args = require "prosody.util.argparse".parse;
+local tc = require "prosody.util.termcolours";
+local isatty = require "prosody.util.pposix".isatty;
+local term_width = require"prosody.util.human.io".term_width;
local have_readline, readline = pcall(require, "readline");
-local adminstream = require "util.adminstream";
+local adminstream = require "prosody.util.adminstream";
if have_readline then
readline.set_readline_name("prosody");
@@ -27,7 +29,7 @@ local function read_line(prompt_string)
end
local function send_line(client, line)
- client.send(st.stanza("repl-input"):text(line));
+ client.send(st.stanza("repl-input", { width = tostring(term_width()) }):text(line));
end
local function repl(client)
@@ -64,6 +66,7 @@ end
local function start(arg) --luacheck: ignore 212/arg
local client = adminstream.client();
local opts, err, where = parse_args(arg);
+ local ttyout = isatty(io.stdout);
if not opts then
if err == "param-not-found" then
@@ -76,24 +79,36 @@ local function start(arg) --luacheck: ignore 212/arg
if arg[1] then
if arg[2] then
- -- prosodyctl shell module reload foo bar.com --> module:reload("foo", "bar.com")
- -- COMPAT Lua 5.1 doesn't have the separator argument to string.rep
- arg[1] = string.format("%s:%s("..string.rep("%q, ", #arg-2):sub(1, -3)..")", unpack(arg));
+ local fmt = { "%s"; ":%s("; ")" };
+ for i = 3, #arg do
+ if arg[i]:sub(1, 1) == ":" then
+ table.insert(fmt, i, ")%s(");
+ elseif i > 3 and fmt[i - 1] == "%q" then
+ table.insert(fmt, i, ", %q");
+ else
+ table.insert(fmt, i, "%q");
+ end
+ end
+ arg[1] = string.format(table.concat(fmt), table.unpack(arg));
end
client.events.add_handler("connected", function()
- client.send(st.stanza("repl-input"):text(arg[1]));
+ send_line(client, arg[1]);
return true;
end, 1);
local errors = 0; -- TODO This is weird, but works for now.
client.events.add_handler("received", function(stanza)
if stanza.name == "repl-output" or stanza.name == "repl-result" then
+ local dest = io.stdout;
if stanza.attr.type == "error" then
errors = errors + 1;
- io.stderr:write(stanza:get_text(), "\n");
+ dest = io.stderr;
+ end
+ if stanza.attr.eol == "0" then
+ dest:write(stanza:get_text());
else
- print(stanza:get_text());
+ dest:write(stanza:get_text(), "\n");
end
end
if stanza.name == "repl-result" then
@@ -118,7 +133,11 @@ local function start(arg) --luacheck: ignore 212/arg
client.events.add_handler("received", function (stanza)
if stanza.name == "repl-output" or stanza.name == "repl-result" then
local result_prefix = stanza.attr.type == "error" and "!" or "|";
- print(result_prefix.." "..stanza:get_text());
+ local out = result_prefix.." "..stanza:get_text();
+ if ttyout and stanza.attr.type == "error" then
+ out = tc.getstring(tc.getstyle("red"), out);
+ end
+ print(out);
end
if stanza.name == "repl-result" then
repl(client);