aboutsummaryrefslogtreecommitdiffstats
path: root/util
diff options
context:
space:
mode:
Diffstat (limited to 'util')
-rw-r--r--util/array.lua16
-rw-r--r--util/bitcompat.lua14
-rw-r--r--util/datetime.lua27
-rw-r--r--util/dependencies.lua6
-rw-r--r--util/envload.lua39
-rw-r--r--util/format.lua7
-rw-r--r--util/hmac.lua4
-rw-r--r--util/human/io.lua5
-rw-r--r--util/human/units.lua10
-rw-r--r--util/import.lua2
-rw-r--r--util/iterators.lua7
-rw-r--r--util/jid.lua1
-rw-r--r--util/jwt.lua202
-rw-r--r--util/logger.lua16
-rw-r--r--util/multitable.lua2
-rw-r--r--util/openmetrics.lua5
-rw-r--r--util/paseto.lua109
-rw-r--r--util/promise.lua7
-rw-r--r--util/prosodyctl/check.lua2
-rw-r--r--util/prosodyctl/shell.lua22
-rw-r--r--util/roles.lua110
-rw-r--r--util/sasl/scram.lua2
-rw-r--r--util/session.lua6
-rw-r--r--util/sslconfig.lua73
-rw-r--r--util/stanza.lua96
-rw-r--r--util/vcard.lua574
-rw-r--r--util/watchdog.lua39
-rw-r--r--util/x509.lua12
28 files changed, 658 insertions, 757 deletions
diff --git a/util/array.lua b/util/array.lua
index c33a5ef1..9d438940 100644
--- a/util/array.lua
+++ b/util/array.lua
@@ -8,6 +8,7 @@
local t_insert, t_sort, t_remove, t_concat
= table.insert, table.sort, table.remove, table.concat;
+local t_move = require "util.table".move;
local setmetatable = setmetatable;
local getmetatable = getmetatable;
@@ -137,13 +138,11 @@ function array_base.slice(outa, ina, i, j)
return outa;
end
- for idx = 1, 1+j-i do
- outa[idx] = ina[i+(idx-1)];
- end
+
+ t_move(ina, i, j, 1, outa);
if ina == outa then
- for idx = 2+j-i, #outa do
- outa[idx] = nil;
- end
+ -- Clear (nil) remainder of range
+ t_move(ina, #outa+1, #outa*2, 2+j-i, ina);
end
return outa;
end
@@ -209,10 +208,7 @@ function array_methods:shuffle()
end
function array_methods:append(ina)
- local len, len2 = #self, #ina;
- for i = 1, len2 do
- self[len+i] = ina[i];
- end
+ t_move(ina, 1, #ina, #self+1, self);
return self;
end
diff --git a/util/bitcompat.lua b/util/bitcompat.lua
index 454181af..8f227354 100644
--- a/util/bitcompat.lua
+++ b/util/bitcompat.lua
@@ -5,12 +5,6 @@
-- Lua 5.2 has it by default
if _G.bit32 then
return _G.bit32;
-else
- -- Lua 5.1 may have it as a standalone module that can be installed
- local ok, bitop = pcall(require, "bit32")
- if ok then
- return bitop;
- end
end
do
@@ -21,12 +15,4 @@ do
end
end
-do
- -- Lastly, try the LuaJIT bitop library
- local ok, bitop = pcall(require, "bit")
- if ok then
- return bitop;
- end
-end
-
error "No bit module found. See https://prosody.im/doc/depends#bitop";
diff --git a/util/datetime.lua b/util/datetime.lua
index 2d27ece4..6df146f4 100644
--- a/util/datetime.lua
+++ b/util/datetime.lua
@@ -12,31 +12,41 @@
local os_date = os.date;
local os_time = os.time;
local os_difftime = os.difftime;
+local floor = math.floor;
local tonumber = tonumber;
local _ENV = nil;
-- luacheck: std none
local function date(t)
- return os_date("!%Y-%m-%d", t);
+ return os_date("!%Y-%m-%d", t and floor(t) or nil);
end
local function datetime(t)
- return os_date("!%Y-%m-%dT%H:%M:%SZ", t);
+ if t == nil or t % 1 == 0 then
+ return os_date("!%Y-%m-%dT%H:%M:%SZ", t);
+ end
+ local m = t % 1;
+ local s = floor(t);
+ return os_date("!%Y-%m-%dT%H:%M:%S.%%06dZ", s):format(floor(m * 1000000));
end
local function time(t)
- return os_date("!%H:%M:%S", t);
+ if t == nil or t % 1 == 0 then
+ return os_date("!%H:%M:%S", t);
+ end
+ local m = t % 1;
+ local s = floor(t);
+ return os_date("!%H:%M:%S.%%06d", s):format(floor(m * 1000000));
end
local function legacy(t)
- return os_date("!%Y%m%dT%H:%M:%S", t);
+ return os_date("!%Y%m%dT%H:%M:%S", t and floor(t) or nil);
end
local function parse(s)
if s then
- local year, month, day, hour, min, sec, tzd;
- year, month, day, hour, min, sec, tzd = s:match("^(%d%d%d%d)%-?(%d%d)%-?(%d%d)T(%d%d):(%d%d):(%d%d)%.?%d*([Z+%-]?.*)$");
+ local year, month, day, hour, min, sec, tzd = s:match("^(%d%d%d%d)%-?(%d%d)%-?(%d%d)T(%d%d):(%d%d):(%d%d%.?%d*)([Z+%-]?.*)$");
if year then
local now = os_time();
local time_offset = os_difftime(os_time(os_date("*t", now)), os_time(os_date("!*t", now))); -- to deal with local timezone
@@ -49,8 +59,9 @@ local function parse(s)
tzd_offset = h * 60 * 60 + m * 60;
if sign == "-" then tzd_offset = -tzd_offset; end
end
- sec = (sec + time_offset) - tzd_offset;
- return os_time({year=year, month=month, day=day, hour=hour, min=min, sec=sec, isdst=false});
+ local prec = sec%1;
+ sec = floor(sec + time_offset) - tzd_offset;
+ return os_time({year=year, month=month, day=day, hour=hour, min=min, sec=sec, isdst=false})+prec;
end
end
end
diff --git a/util/dependencies.lua b/util/dependencies.lua
index d7836404..165468c5 100644
--- a/util/dependencies.lua
+++ b/util/dependencies.lua
@@ -32,10 +32,10 @@ local function missingdep(name, sources, msg, err) -- luacheck: ignore err
end
local function check_dependencies()
- if _VERSION < "Lua 5.1" then
+ if _VERSION < "Lua 5.2" then
print "***********************************"
print("Unsupported Lua version: ".._VERSION);
- print("At least Lua 5.1 is required.");
+ print("At least Lua 5.2 is required.");
print "***********************************"
return false;
end
@@ -155,7 +155,7 @@ local function log_warnings()
if _VERSION > "Lua 5.4" then
prosody.log("warn", "Support for %s is experimental, please report any issues", _VERSION);
elseif _VERSION < "Lua 5.2" then
- prosody.log("warn", "%s has several issues and support is being phased out, consider upgrading", _VERSION);
+ prosody.log("warn", "%s support is deprecated, upgrade as soon as possible", _VERSION);
end
local ssl = softreq"ssl";
if ssl then
diff --git a/util/envload.lua b/util/envload.lua
index 6182a1f9..cf45b702 100644
--- a/util/envload.lua
+++ b/util/envload.lua
@@ -6,38 +6,19 @@
--
-- luacheck: ignore 113/setfenv 113/loadstring
-local load, loadstring, setfenv = load, loadstring, setfenv;
+local load = load;
local io_open = io.open;
-local envload;
-local envloadfile;
-if setfenv then
- function envload(code, source, env)
- local f, err = loadstring(code, source);
- if f and env then setfenv(f, env); end
- return f, err;
- end
-
- function envloadfile(file, env)
- local fh, err, errno = io_open(file);
- if not fh then return fh, err, errno; end
- local f, err = load(function () return fh:read(2048); end, "@"..file);
- fh:close();
- if f and env then setfenv(f, env); end
- return f, err;
- end
-else
- function envload(code, source, env)
- return load(code, source, nil, env);
- end
+local function envload(code, source, env)
+ return load(code, source, nil, env);
+end
- function envloadfile(file, env)
- local fh, err, errno = io_open(file);
- if not fh then return fh, err, errno; end
- local f, err = load(fh:lines(2048), "@"..file, nil, env);
- fh:close();
- return f, err;
- end
+local function envloadfile(file, env)
+ local fh, err, errno = io_open(file);
+ if not fh then return fh, err, errno; end
+ local f, err = load(fh:lines(2048), "@" .. file, nil, env);
+ fh:close();
+ return f, err;
end
return { envload = envload, envloadfile = envloadfile };
diff --git a/util/format.lua b/util/format.lua
index d709aada..203bdeab 100644
--- a/util/format.lua
+++ b/util/format.lua
@@ -6,8 +6,8 @@
-- Provides some protection from e.g. CAPEC-135, CWE-117, CWE-134, CWE-93
local tostring = tostring;
-local unpack = table.unpack or unpack; -- luacheck: ignore 113/unpack
-local pack = require "util.table".pack; -- TODO table.pack in 5.2+
+local unpack = table.unpack;
+local pack = table.pack;
local valid_utf8 = require "util.encodings".utf8.valid;
local type = type;
local dump = require "util.serialization".new("debug");
@@ -35,7 +35,6 @@ local control_symbols = {
["\030"] = "\226\144\158", ["\031"] = "\226\144\159", ["\127"] = "\226\144\161",
};
local supports_p = pcall(string.format, "%p", ""); -- >= Lua 5.4
-local supports_a = pcall(string.format, "%a", 0.0); -- > Lua 5.1
local function format(formatstring, ...)
local args = pack(...);
@@ -93,8 +92,6 @@ local function format(formatstring, ...)
elseif expects_positive[option] and arg < 0 then
args[i] = tostring(arg);
return "[%s]";
- elseif (option == "a" or option == "A") and not supports_a then
- return "%x";
else
return -- acceptable number
end
diff --git a/util/hmac.lua b/util/hmac.lua
index 4cad17cc..ca030259 100644
--- a/util/hmac.lua
+++ b/util/hmac.lua
@@ -13,6 +13,10 @@ local hashes = require "util.hashes"
return {
md5 = hashes.hmac_md5,
sha1 = hashes.hmac_sha1,
+ sha224 = hashes.hmac_sha224,
sha256 = hashes.hmac_sha256,
+ sha384 = hashes.hmac_sha384,
sha512 = hashes.hmac_sha512,
+ blake2s256 = hashes.hmac_blake2s256,
+ blake2b512 = hashes.hmac_blake2b512,
};
diff --git a/util/human/io.lua b/util/human/io.lua
index 7d7dea97..4fce0e94 100644
--- a/util/human/io.lua
+++ b/util/human/io.lua
@@ -30,10 +30,7 @@ local function getline()
end
local function getpass()
- local stty_ret, _, status_code = os.execute("stty -echo 2>/dev/null");
- if status_code then -- COMPAT w/ Lua 5.1
- stty_ret = status_code;
- end
+ local stty_ret = os.execute("stty -echo 2>/dev/null");
if stty_ret ~= 0 then
io.write("\027[08m"); -- ANSI 'hidden' text attribute
end
diff --git a/util/human/units.lua b/util/human/units.lua
index af233e98..329c8518 100644
--- a/util/human/units.lua
+++ b/util/human/units.lua
@@ -4,15 +4,7 @@ local math_floor = math.floor;
local math_log = math.log;
local math_max = math.max;
local math_min = math.min;
-local unpack = table.unpack or unpack; --luacheck: ignore 113
-
-if math_log(10, 10) ~= 1 then
- -- Lua 5.1 COMPAT
- local log10 = math.log10;
- function math_log(n, base)
- return log10(n) / log10(base);
- end
-end
+local unpack = table.unpack;
local large = {
"k", 1000,
diff --git a/util/import.lua b/util/import.lua
index 1007bc0a..0892e9b1 100644
--- a/util/import.lua
+++ b/util/import.lua
@@ -8,7 +8,7 @@
-local unpack = table.unpack or unpack; --luacheck: ignore 113
+local unpack = table.unpack;
local t_insert = table.insert;
function _G.import(module, ...)
local m = package.loaded[module] or require(module);
diff --git a/util/iterators.lua b/util/iterators.lua
index c03c2fd6..eb4c54af 100644
--- a/util/iterators.lua
+++ b/util/iterators.lua
@@ -12,8 +12,8 @@ local it = {};
local t_insert = table.insert;
local next = next;
-local unpack = table.unpack or unpack; --luacheck: ignore 113
-local pack = table.pack or require "util.table".pack;
+local unpack = table.unpack;
+local pack = table.pack;
local type = type;
local table, setmetatable = table, setmetatable;
@@ -240,7 +240,8 @@ function join_methods:prepend(f, s, var)
end
function it.join(f, s, var)
- return setmetatable({ {f, s, var} }, join_mt);
+ local t = setmetatable({ {f, s, var} }, join_mt);
+ return t, { t, 1 };
end
return it;
diff --git a/util/jid.lua b/util/jid.lua
index 694a6b1f..759af746 100644
--- a/util/jid.lua
+++ b/util/jid.lua
@@ -111,6 +111,7 @@ local function resource(jid)
return (select(3, split(jid)));
end
+-- TODO Forbid \20 at start and end of escaped output per XEP-0106 v1.1
local function escape(s) return s and (s:gsub("\\%x%x", backslash_escapes):gsub("[\"&'/:<>@ ]", escapes)); end
local function unescape(s) return s and (s:gsub("\\%x%x", unescapes)); end
diff --git a/util/jwt.lua b/util/jwt.lua
index bf106dfa..42a9f7f2 100644
--- a/util/jwt.lua
+++ b/util/jwt.lua
@@ -1,4 +1,5 @@
local s_gsub = string.gsub;
+local crypto = require "util.crypto";
local json = require "util.json";
local hashes = require "util.hashes";
local base64_encode = require "util.encodings".base64.encode;
@@ -13,17 +14,8 @@ local function unb64url(data)
return base64_decode(s_gsub(data, "[-_]", b64url_rep).."==");
end
-local static_header = b64url('{"alg":"HS256","typ":"JWT"}') .. '.';
-
-local function sign(key, payload)
- local encoded_payload = json.encode(payload);
- local signed = static_header .. b64url(encoded_payload);
- local signature = hashes.hmac_sha256(key, signed);
- return signed .. "." .. b64url(signature);
-end
-
local jwt_pattern = "^(([A-Za-z0-9-_]+)%.([A-Za-z0-9-_]+))%.([A-Za-z0-9-_]+)$"
-local function verify(key, blob)
+local function decode_jwt(blob, expected_alg)
local signed, bheader, bpayload, signature = string.match(blob, jwt_pattern);
if not signed then
return nil, "invalid-encoding";
@@ -31,21 +23,197 @@ local function verify(key, blob)
local header = json.decode(unb64url(bheader));
if not header or type(header) ~= "table" then
return nil, "invalid-header";
- elseif header.alg ~= "HS256" then
+ elseif header.alg ~= expected_alg then
return nil, "unsupported-algorithm";
end
- if not secure_equals(b64url(hashes.hmac_sha256(key, signed)), signature) then
- return false, "signature-mismatch";
- end
- local payload, err = json.decode(unb64url(bpayload));
+ return signed, signature, bpayload;
+end
+
+local function new_static_header(algorithm_name)
+ return b64url('{"alg":"'..algorithm_name..'","typ":"JWT"}') .. '.';
+end
+
+local function decode_raw_payload(raw_payload)
+ local payload, err = json.decode(unb64url(raw_payload));
if err ~= nil then
return nil, "json-decode-error";
+ elseif type(payload) ~= "table" then
+ return nil, "invalid-payload-type";
end
return true, payload;
end
+-- HS*** family
+local function new_hmac_algorithm(name)
+ local static_header = new_static_header(name);
+
+ local hmac = hashes["hmac_sha"..name:sub(-3)];
+
+ local function sign(key, payload)
+ local encoded_payload = json.encode(payload);
+ local signed = static_header .. b64url(encoded_payload);
+ local signature = hmac(key, signed);
+ return signed .. "." .. b64url(signature);
+ end
+
+ local function verify(key, blob)
+ local signed, signature, raw_payload = decode_jwt(blob, name);
+ if not signed then return nil, signature; end -- nil, err
+
+ if not secure_equals(b64url(hmac(key, signed)), signature) then
+ return false, "signature-mismatch";
+ end
+
+ return decode_raw_payload(raw_payload);
+ end
+
+ local function load_key(key)
+ assert(type(key) == "string", "key must be string (long, random, secure)");
+ return key;
+ end
+
+ return { sign = sign, verify = verify, load_key = load_key };
+end
+
+local function new_crypto_algorithm(name, key_type, c_sign, c_verify, sig_encode, sig_decode)
+ local static_header = new_static_header(name);
+
+ return {
+ sign = function (private_key, payload)
+ local encoded_payload = json.encode(payload);
+ local signed = static_header .. b64url(encoded_payload);
+
+ local signature = c_sign(private_key, signed);
+ if sig_encode then
+ signature = sig_encode(signature);
+ end
+
+ return signed.."."..b64url(signature);
+ end;
+
+ verify = function (public_key, blob)
+ local signed, signature, raw_payload = decode_jwt(blob, name);
+ if not signed then return nil, signature; end -- nil, err
+
+ signature = unb64url(signature);
+ if sig_decode and signature then
+ signature = sig_decode(signature);
+ end
+ if not signature then
+ return false, "signature-mismatch";
+ end
+
+ local verify_ok = c_verify(public_key, signed, signature);
+ if not verify_ok then
+ return false, "signature-mismatch";
+ end
+
+ return decode_raw_payload(raw_payload);
+ end;
+
+ load_public_key = function (public_key_pem)
+ local key = assert(crypto.import_public_pem(public_key_pem));
+ assert(key:get_type() == key_type, "incorrect key type");
+ return key;
+ end;
+
+ load_private_key = function (private_key_pem)
+ local key = assert(crypto.import_private_pem(private_key_pem));
+ assert(key:get_type() == key_type, "incorrect key type");
+ return key;
+ end;
+ };
+end
+
+-- RS***, PS***
+local rsa_sign_algos = { RS = "rsassa_pkcs1", PS = "rsassa_pss" };
+local function new_rsa_algorithm(name)
+ local family, digest_bits = name:match("^(..)(...)$");
+ local c_sign = crypto[rsa_sign_algos[family].."_sha"..digest_bits.."_sign"];
+ local c_verify = crypto[rsa_sign_algos[family].."_sha"..digest_bits.."_verify"];
+ return new_crypto_algorithm(name, "rsaEncryption", c_sign, c_verify);
+end
+
+-- ES***
+local function new_ecdsa_algorithm(name, c_sign, c_verify, sig_bytes)
+ local function encode_ecdsa_sig(der_sig)
+ local r, s = crypto.parse_ecdsa_signature(der_sig, sig_bytes);
+ return r..s;
+ end
+
+ local expected_sig_length = sig_bytes*2;
+ local function decode_ecdsa_sig(jwk_sig)
+ if #jwk_sig ~= expected_sig_length then
+ return nil;
+ end
+ return crypto.build_ecdsa_signature(jwk_sig:sub(1, sig_bytes), jwk_sig:sub(sig_bytes+1));
+ end
+ return new_crypto_algorithm(name, "id-ecPublicKey", c_sign, c_verify, encode_ecdsa_sig, decode_ecdsa_sig);
+end
+
+local algorithms = {
+ HS256 = new_hmac_algorithm("HS256"), HS384 = new_hmac_algorithm("HS384"), HS512 = new_hmac_algorithm("HS512");
+ ES256 = new_ecdsa_algorithm("ES256", crypto.ecdsa_sha256_sign, crypto.ecdsa_sha256_verify, 32);
+ ES512 = new_ecdsa_algorithm("ES512", crypto.ecdsa_sha512_sign, crypto.ecdsa_sha512_verify, 66);
+ RS256 = new_rsa_algorithm("RS256"), RS384 = new_rsa_algorithm("RS384"), RS512 = new_rsa_algorithm("RS512");
+ PS256 = new_rsa_algorithm("PS256"), PS384 = new_rsa_algorithm("PS384"), PS512 = new_rsa_algorithm("PS512");
+};
+
+local function new_signer(algorithm, key_input, options)
+ local impl = assert(algorithms[algorithm], "Unknown JWT algorithm: "..algorithm);
+ local key = (impl.load_private_key or impl.load_key)(key_input);
+ local sign = impl.sign;
+ local default_ttl = (options and options.default_ttl) or 3600;
+ return function (payload)
+ local issued_at;
+ if not payload.iat then
+ issued_at = os.time();
+ payload.iat = issued_at;
+ end
+ if not payload.exp then
+ payload.exp = (issued_at or os.time()) + default_ttl;
+ end
+ return sign(key, payload);
+ end
+end
+
+local function new_verifier(algorithm, key_input, options)
+ local impl = assert(algorithms[algorithm], "Unknown JWT algorithm: "..algorithm);
+ local key = (impl.load_public_key or impl.load_key)(key_input);
+ local verify = impl.verify;
+ local check_expiry = not (options and options.accept_expired);
+ local claim_verifier = options and options.claim_verifier;
+ return function (token)
+ local ok, payload = verify(key, token);
+ if ok then
+ local expires_at = check_expiry and payload.exp;
+ if expires_at then
+ if type(expires_at) ~= "number" then
+ return nil, "invalid-expiry";
+ elseif expires_at < os.time() then
+ return nil, "token-expired";
+ end
+ end
+ if claim_verifier and not claim_verifier(payload) then
+ return nil, "incorrect-claims";
+ end
+ end
+ return ok, payload;
+ end
+end
+
+local function init(algorithm, private_key, public_key, options)
+ return new_signer(algorithm, private_key, options), new_verifier(algorithm, public_key or private_key, options);
+end
+
return {
- sign = sign;
- verify = verify;
+ init = init;
+ new_signer = new_signer;
+ new_verifier = new_verifier;
+ -- Exported mainly for tests
+ _algorithms = algorithms;
+ -- Deprecated
+ sign = algorithms.HS256.sign;
+ verify = algorithms.HS256.verify;
};
diff --git a/util/logger.lua b/util/logger.lua
index 20a5cef2..148b98dc 100644
--- a/util/logger.lua
+++ b/util/logger.lua
@@ -10,6 +10,7 @@
local pairs = pairs;
local ipairs = ipairs;
local require = require;
+local t_remove = table.remove;
local _ENV = nil;
-- luacheck: std none
@@ -78,6 +79,20 @@ local function add_simple_sink(simple_sink_function, levels)
for _, level in ipairs(levels or {"debug", "info", "warn", "error"}) do
add_level_sink(level, sink_function);
end
+ return sink_function;
+end
+
+local function remove_sink(sink_function)
+ local removed;
+ for level, sinks in pairs(level_sinks) do
+ for i = #sinks, 1, -1 do
+ if sinks[i] == sink_function then
+ t_remove(sinks, i);
+ removed = true;
+ end
+ end
+ end
+ return removed;
end
return {
@@ -87,4 +102,5 @@ return {
add_level_sink = add_level_sink;
add_simple_sink = add_simple_sink;
new = make_logger;
+ remove_sink = remove_sink;
};
diff --git a/util/multitable.lua b/util/multitable.lua
index 4f2cd972..0c292b45 100644
--- a/util/multitable.lua
+++ b/util/multitable.lua
@@ -9,7 +9,7 @@
local select = select;
local t_insert = table.insert;
local pairs, next, type = pairs, next, type;
-local unpack = table.unpack or unpack; --luacheck: ignore 113
+local unpack = table.unpack;
local _ENV = nil;
-- luacheck: std none
diff --git a/util/openmetrics.lua b/util/openmetrics.lua
index c18e63e9..8c77ffcd 100644
--- a/util/openmetrics.lua
+++ b/util/openmetrics.lua
@@ -26,7 +26,7 @@ local log = require "util.logger".init("util.openmetrics");
local new_multitable = require "util.multitable".new;
local iter_multitable = require "util.multitable".iter;
local t_concat, t_insert = table.concat, table.insert;
-local t_pack, t_unpack = require "util.table".pack, table.unpack or unpack; --luacheck: ignore 113/unpack
+local t_pack, t_unpack = table.pack, table.unpack;
-- BEGIN of Utility: "metric proxy"
-- This allows to wrap a MetricFamily in a proxy which only provides the
@@ -35,6 +35,7 @@ local t_pack, t_unpack = require "util.table".pack, table.unpack or unpack; --lu
-- `with_partial_label` by the moduleapi in order to pre-set the `host` label
-- on metrics created in non-global modules.
local metric_proxy_mt = {}
+metric_proxy_mt.__name = "metric_proxy"
metric_proxy_mt.__index = metric_proxy_mt
local function new_metric_proxy(metric_family, with_labels_proxy_fun)
@@ -128,6 +129,7 @@ end
-- BEGIN of generic MetricFamily implementation
local metric_family_mt = {}
+metric_family_mt.__name = "metric_family"
metric_family_mt.__index = metric_family_mt
local function histogram_metric_ctor(orig_ctor, buckets)
@@ -278,6 +280,7 @@ local function compose_name(name, unit)
end
local metric_registry_mt = {}
+metric_registry_mt.__name = "metric_registry"
metric_registry_mt.__index = metric_registry_mt
local function new_metric_registry(backend)
diff --git a/util/paseto.lua b/util/paseto.lua
new file mode 100644
index 00000000..8b564c96
--- /dev/null
+++ b/util/paseto.lua
@@ -0,0 +1,109 @@
+local crypto = require "util.crypto";
+local json = require "util.json";
+local base64_encode = require "util.encodings".base64.encode;
+local base64_decode = require "util.encodings".base64.decode;
+local secure_equals = require "util.hashes".equals;
+local bit = require "util.bitcompat";
+local s_pack = require "util.struct".pack;
+
+local s_gsub = string.gsub;
+
+local v4_public = {};
+
+local b64url_rep = { ["+"] = "-", ["/"] = "_", ["="] = "", ["-"] = "+", ["_"] = "/" };
+local function b64url(data)
+ return (s_gsub(base64_encode(data), "[+/=]", b64url_rep));
+end
+local function unb64url(data)
+ return base64_decode(s_gsub(data, "[-_]", b64url_rep).."==");
+end
+
+local function le64(n)
+ return s_pack("<I8", bit.band(n, 0x7F));
+end
+
+local function pae(parts)
+ if type(parts) ~= "table" then
+ error("bad argument #1 to 'pae' (table expected, got "..type(parts)..")");
+ end
+ local o = { le64(#parts) };
+ for _, part in ipairs(parts) do
+ table.insert(o, le64(#part)..part);
+ end
+ return table.concat(o);
+end
+
+function v4_public.sign(m, sk, f, i)
+ if type(m) ~= "table" then
+ return nil, "PASETO payloads must be a table";
+ end
+ m = json.encode(m);
+ local h = "v4.public.";
+ local m2 = pae({ h, m, f or "", i or "" });
+ local sig = crypto.ed25519_sign(sk, m2);
+ if not f or f == "" then
+ return h..b64url(m..sig);
+ else
+ return h..b64url(m..sig).."."..b64url(f);
+ end
+end
+
+function v4_public.verify(tok, pk, expected_f, i)
+ local h, sm, f = tok:match("^(v4%.public%.)([^%.]+)%.?(.*)$");
+ if not h then
+ return nil, "invalid-token-format";
+ end
+ f = f and unb64url(f) or nil;
+ if expected_f then
+ if not f or not secure_equals(expected_f, f) then
+ return nil, "invalid-footer";
+ end
+ end
+ local raw_sm = unb64url(sm);
+ if not raw_sm or #raw_sm <= 64 then
+ return nil, "invalid-token-format";
+ end
+ local s, m = raw_sm:sub(-64), raw_sm:sub(1, -65);
+ local m2 = pae({ h, m, f or "", i or "" });
+ local ok = crypto.ed25519_verify(pk, m2, s);
+ if not ok then
+ return nil, "invalid-token";
+ end
+ local payload, err = json.decode(m);
+ if err ~= nil or type(payload) ~= "table" then
+ return nil, "json-decode-error";
+ end
+ return payload;
+end
+
+v4_public.import_private_key = crypto.import_private_pem;
+v4_public.import_public_key = crypto.import_public_pem;
+function v4_public.new_keypair()
+ return crypto.generate_ed25519_keypair();
+end
+
+function v4_public.init(private_key_pem, public_key_pem, options)
+ local sign, verify = v4_public.sign, v4_public.verify;
+ local public_key = public_key_pem and v4_public.import_public_key(public_key_pem);
+ local private_key = private_key_pem and v4_public.import_private_key(private_key_pem);
+ local default_footer = options and options.default_footer;
+ local default_assertion = options and options.default_implicit_assertion;
+ return private_key and function (token, token_footer, token_assertion)
+ return sign(token, private_key, token_footer or default_footer, token_assertion or default_assertion);
+ end, public_key and function (token, expected_footer, token_assertion)
+ return verify(token, public_key, expected_footer or default_footer, token_assertion or default_assertion);
+ end;
+end
+
+function v4_public.new_signer(private_key_pem, options)
+ return (v4_public.init(private_key_pem, nil, options));
+end
+
+function v4_public.new_verifier(public_key_pem, options)
+ return (select(2, v4_public.init(public_key_pem, options)));
+end
+
+return {
+ pae = pae;
+ v4_public = v4_public;
+};
diff --git a/util/promise.lua b/util/promise.lua
index c4e166ed..f56502d2 100644
--- a/util/promise.lua
+++ b/util/promise.lua
@@ -2,7 +2,7 @@ local promise_methods = {};
local promise_mt = { __name = "promise", __index = promise_methods };
local xpcall = require "util.xpcall".xpcall;
-local unpack = table.unpack or unpack; --luacheck: ignore 113
+local unpack = table.unpack;
function promise_mt:__tostring()
return "promise (" .. (self._state or "invalid") .. ")";
@@ -57,10 +57,7 @@ local function promise_settle(promise, new_state, new_next, cbs, value)
end
local function new_resolve_functions(p)
- local resolved = false;
local function _resolve(v)
- if resolved then return; end
- resolved = true;
if is_promise(v) then
v:next(new_resolve_functions(p));
elseif promise_settle(p, "fulfilled", next_fulfilled, p._pending_on_fulfilled, v) then
@@ -69,8 +66,6 @@ local function new_resolve_functions(p)
end
local function _reject(e)
- if resolved then return; end
- resolved = true;
if promise_settle(p, "rejected", next_rejected, p._pending_on_rejected, e) then
p.reason = e;
end
diff --git a/util/prosodyctl/check.lua b/util/prosodyctl/check.lua
index 42d73f29..3f89f930 100644
--- a/util/prosodyctl/check.lua
+++ b/util/prosodyctl/check.lua
@@ -155,7 +155,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());
diff --git a/util/prosodyctl/shell.lua b/util/prosodyctl/shell.lua
index bce27b94..5f99bec1 100644
--- a/util/prosodyctl/shell.lua
+++ b/util/prosodyctl/shell.lua
@@ -4,6 +4,8 @@ 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 tc = require "util.termcolours";
+local isatty = require "util.pposix".isatty;
local have_readline, readline = pcall(require, "readline");
@@ -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 = os.getenv "COLUMNS" }):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
@@ -77,8 +80,7 @@ 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));
+ arg[1] = string.format("%s:%s("..string.rep("%q", #arg-2,", ")..")", unpack(arg));
end
client.events.add_handler("connected", function()
@@ -89,11 +91,15 @@ local function start(arg) --luacheck: ignore 212/arg
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 +124,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);
diff --git a/util/roles.lua b/util/roles.lua
new file mode 100644
index 00000000..2c3a5026
--- /dev/null
+++ b/util/roles.lua
@@ -0,0 +1,110 @@
+local array = require "util.array";
+local it = require "util.iterators";
+local new_short_id = require "util.id".short;
+
+local role_methods = {};
+local role_mt = {
+ __index = role_methods;
+ __name = "role";
+ __add = nil;
+};
+
+local function is_role(o)
+ local mt = getmetatable(o);
+ return mt == role_mt;
+end
+
+local function _new_may(permissions, inherited_mays)
+ local n_inherited = inherited_mays and #inherited_mays;
+ return function (role, action, context)
+ -- Note: 'role' may be a descendent role, not only the one we're attached to
+ local policy = permissions[action];
+ if policy ~= nil then
+ return policy;
+ end
+ if n_inherited then
+ for i = 1, n_inherited do
+ policy = inherited_mays[i](role, action, context);
+ if policy ~= nil then
+ return policy;
+ end
+ end
+ end
+ return nil;
+ end
+end
+
+local permissions_key = {};
+
+-- {
+-- Required:
+-- name = "My fancy role";
+--
+-- Optional:
+-- inherits = { role_obj... }
+-- default = true
+-- priority = 100
+-- permissions = {
+-- ["foo"] = true; -- allow
+-- ["bar"] = false; -- deny
+-- }
+-- }
+local function new(base_config, overrides)
+ local config = setmetatable(overrides or {}, { __index = base_config });
+ local permissions = {};
+ local inherited_mays;
+ if config.inherits then
+ inherited_mays = array.pluck(config.inherits, "may");
+ end
+ local new_role = {
+ id = new_short_id();
+ name = config.name;
+ description = config.description;
+ default = config.default;
+ priority = config.priority;
+ may = _new_may(permissions, inherited_mays);
+ inherits = config.inherits;
+ [permissions_key] = permissions;
+ };
+ local desired_permissions = config.permissions or config[permissions_key];
+ for k, v in pairs(desired_permissions or {}) do
+ permissions[k] = v;
+ end
+ return setmetatable(new_role, role_mt);
+end
+
+function role_methods:clone(overrides)
+ return new(self, overrides);
+end
+
+function role_methods:set_permission(permission_name, policy, overwrite)
+ local permissions = self[permissions_key];
+ if overwrite ~= true and permissions[permission_name] ~= nil and permissions[permission_name] ~= policy then
+ return false, "policy-already-exists";
+ end
+ permissions[permission_name] = policy;
+ return true;
+end
+
+function role_methods:policies()
+ local policy_iterator, s, v = it.join(pairs(self[permissions_key]));
+ if self.inherits then
+ for _, inherited_role in ipairs(self.inherits) do
+ policy_iterator:append(inherited_role:policies());
+ end
+ end
+ return policy_iterator, s, v;
+end
+
+function role_mt.__tostring(self)
+ return ("role<[%s] %s>"):format(self.id or "nil", self.name or "[no name]");
+end
+
+function role_mt.__pairs(self)
+ return it.filter(permissions_key, next, self);
+end
+
+return {
+ is_role = is_role;
+ new = new;
+};
diff --git a/util/sasl/scram.lua b/util/sasl/scram.lua
index 37abf4a4..4606d1fd 100644
--- a/util/sasl/scram.lua
+++ b/util/sasl/scram.lua
@@ -240,7 +240,7 @@ local function init(registerMechanism)
-- register channel binding equivalent
registerMechanism("SCRAM-"..hash_name.."-PLUS",
{"plain", "scram_"..(hashprep(hash_name))},
- scram_gen(hash_name:lower(), hash, hmac_hash, get_auth_db, true), {"tls-unique"});
+ scram_gen(hash_name:lower(), hash, hmac_hash, get_auth_db, true), {"tls-unique", "tls-exporter"});
end
registerSCRAMMechanism("SHA-1", hashes.sha1, hashes.hmac_sha1, hashes.pbkdf2_hmac_sha1);
diff --git a/util/session.lua b/util/session.lua
index 25b22faf..d908476a 100644
--- a/util/session.lua
+++ b/util/session.lua
@@ -57,10 +57,16 @@ local function set_send(session)
return session;
end
+local function set_role(session, role)
+ session.role = role;
+end
+
return {
new = new_session;
+
set_id = set_id;
set_logger = set_logger;
set_conn = set_conn;
set_send = set_send;
+ set_role = set_role;
}
diff --git a/util/sslconfig.lua b/util/sslconfig.lua
index 6074a1fb..0078365b 100644
--- a/util/sslconfig.lua
+++ b/util/sslconfig.lua
@@ -3,9 +3,12 @@
local type = type;
local pairs = pairs;
local rawset = rawset;
+local rawget = rawget;
+local error = error;
local t_concat = table.concat;
local t_insert = table.insert;
local setmetatable = setmetatable;
+local resolve_path = require"util.paths".resolve_relative_path;
local _ENV = nil;
-- luacheck: std none
@@ -34,7 +37,7 @@ function handlers.options(config, field, new)
options[value] = true;
end
end
- config[field] = options;
+ rawset(config, field, options)
end
handlers.verifyext = handlers.options;
@@ -70,6 +73,20 @@ finalisers.curveslist = finalisers.ciphers;
-- TLS 1.3 ciphers
finalisers.ciphersuites = finalisers.ciphers;
+-- Path expansion
+function finalisers.key(path, config)
+ if type(path) == "string" then
+ return resolve_path(config._basedir, path);
+ else
+ return nil
+ end
+end
+finalisers.certificate = finalisers.key;
+finalisers.cafile = finalisers.key;
+finalisers.capath = finalisers.key;
+-- XXX: copied from core/certmanager.lua, but this seems odd, because it would remove a dhparam function from the config
+finalisers.dhparam = finalisers.key;
+
-- protocol = "x" should enable only that protocol
-- protocol = "x+" should enable x and later versions
@@ -89,37 +106,81 @@ end
-- Merge options from 'new' config into 'config'
local function apply(config, new)
+ rawset(config, "_cache", nil);
if type(new) == "table" then
for field, value in pairs(new) do
- (handlers[field] or rawset)(config, field, value);
+ -- exclude keys which are internal to the config builder
+ if field:sub(1, 1) ~= "_" then
+ (handlers[field] or rawset)(config, field, value);
+ end
end
end
+ return config
end
-- Finalize the config into the form LuaSec expects
local function final(config)
local output = { };
for field, value in pairs(config) do
- output[field] = (finalisers[field] or id)(value);
+ -- exclude keys which are internal to the config builder
+ if field:sub(1, 1) ~= "_" then
+ output[field] = (finalisers[field] or id)(value, config);
+ end
end
-- Need to handle protocols last because it adds to the options list
protocol(output);
return output;
end
+local function build(config)
+ local cached = rawget(config, "_cache");
+ if cached then
+ return cached, nil
+ end
+
+ local ctx, err = rawget(config, "_context_factory")(config:final(), config);
+ if ctx then
+ rawset(config, "_cache", ctx);
+ end
+ return ctx, err
+end
+
local sslopts_mt = {
__index = {
apply = apply;
final = final;
+ build = build;
};
+ __newindex = function()
+ error("SSL config objects cannot be modified directly. Use :apply()")
+ end;
};
-local function new()
- return setmetatable({options={}}, sslopts_mt);
+
+-- passing basedir through everything is required to avoid sslconfig depending
+-- on prosody.paths.config
+local function new(context_factory, basedir)
+ return setmetatable({
+ _context_factory = context_factory,
+ _basedir = basedir,
+ options={},
+ }, sslopts_mt);
end
+local function clone(config)
+ local result = new();
+ for k, v in pairs(config) do
+ -- note that we *do* copy the internal keys on clone -- we have to carry
+ -- both the factory and the cache with us
+ rawset(result, k, v);
+ end
+ return result
+end
+
+sslopts_mt.__index.clone = clone;
+
return {
apply = apply;
final = final;
- new = new;
+ _new = new;
};
diff --git a/util/stanza.lua b/util/stanza.lua
index 29551918..c6e7aa6d 100644
--- a/util/stanza.lua
+++ b/util/stanza.lua
@@ -21,12 +21,15 @@ local type = type;
local s_gsub = string.gsub;
local s_sub = string.sub;
local s_find = string.find;
+local t_move = table.move or require "util.table".move;
+local t_create = require"util.table".create;
local valid_utf8 = require "util.encodings".utf8.valid;
local do_pretty_printing, termcolours = pcall(require, "util.termcolours");
local xmlns_stanzas = "urn:ietf:params:xml:ns:xmpp-stanzas";
+local xmpp_stanzas_attr = { xmlns = xmlns_stanzas };
local _ENV = nil;
-- luacheck: std none
@@ -175,6 +178,14 @@ function stanza_mt:get_child_text(name, xmlns)
return nil;
end
+function stanza_mt:get_child_attr(name, xmlns, attr)
+ local tag = self:get_child(name, xmlns);
+ if tag then
+ return tag.attr[attr];
+ end
+ return nil;
+end
+
function stanza_mt:child_with_name(name)
for _, child in ipairs(self.tags) do
if child.name == name then return child; end
@@ -279,25 +290,33 @@ function stanza_mt:find(path)
end
local function _clone(stanza, only_top)
- local attr, tags = {}, {};
+ local attr = {};
for k,v in pairs(stanza.attr) do attr[k] = v; end
local old_namespaces, namespaces = stanza.namespaces;
if old_namespaces then
namespaces = {};
for k,v in pairs(old_namespaces) do namespaces[k] = v; end
end
- local new = { name = stanza.name, attr = attr, namespaces = namespaces, tags = tags };
+ local tags, new;
+ if only_top then
+ tags = {};
+ new = { name = stanza.name, attr = attr, namespaces = namespaces, tags = tags };
+ else
+ tags = t_create(#stanza.tags, 0);
+ new = t_create(#stanza, 4);
+ new.name = stanza.name;
+ new.attr = attr;
+ new.namespaces = namespaces;
+ new.tags = tags;
+ end
+
+ setmetatable(new, stanza_mt);
if not only_top then
- for i=1,#stanza do
- local child = stanza[i];
- if child.name then
- child = _clone(child);
- t_insert(tags, child);
- end
- t_insert(new, child);
- end
+ t_move(stanza, 1, #stanza, 1, new);
+ t_move(stanza.tags, 1, #stanza.tags, 1, tags);
+ new:maptags(_clone);
end
- return setmetatable(new, stanza_mt);
+ return new;
end
local function clone(stanza, only_top)
@@ -383,6 +402,33 @@ function stanza_mt.get_error(stanza)
return error_type, condition or "undefined-condition", text, extra_tag;
end
+function stanza_mt.add_error(stanza, error_type, condition, error_message, error_by)
+ local extra;
+ if type(error_type) == "table" then -- an util.error or similar object
+ if type(error_type.extra) == "table" then
+ extra = error_type.extra;
+ end
+ if type(error_type.context) == "table" and type(error_type.context.by) == "string" then error_by = error_type.context.by; end
+ error_type, condition, error_message = error_type.type, error_type.condition, error_type.text;
+ end
+ if stanza.attr.from == error_by then
+ error_by = nil;
+ end
+ stanza:tag("error", {type = error_type, by = error_by}) --COMPAT: Some day xmlns:stanzas goes here
+ :tag(condition, xmpp_stanzas_attr);
+ if extra and condition == "gone" and type(extra.uri) == "string" then
+ stanza:text(extra.uri);
+ end
+ stanza:up();
+ if error_message then stanza:text_tag("text", error_message, xmpp_stanzas_attr); end
+ if extra and is_stanza(extra.tag) then
+ stanza:add_child(extra.tag);
+ elseif extra and extra.namespace and extra.condition then
+ stanza:tag(extra.condition, { xmlns = extra.namespace }):up();
+ end
+ return stanza:up();
+end
+
local function preserialize(stanza)
local s = { name = stanza.name, attr = stanza.attr };
for _, child in ipairs(stanza) do
@@ -457,7 +503,6 @@ local function reply(orig)
});
end
-local xmpp_stanzas_attr = { xmlns = xmlns_stanzas };
local function error_reply(orig, error_type, condition, error_message, error_by)
if not is_stanza(orig) then
error("bad argument to error_reply: expected stanza, got "..type(orig));
@@ -466,30 +511,9 @@ local function error_reply(orig, error_type, condition, error_message, error_by)
end
local t = reply(orig);
t.attr.type = "error";
- local extra;
- if type(error_type) == "table" then -- an util.error or similar object
- if type(error_type.extra) == "table" then
- extra = error_type.extra;
- end
- if type(error_type.context) == "table" and type(error_type.context.by) == "string" then error_by = error_type.context.by; end
- error_type, condition, error_message = error_type.type, error_type.condition, error_type.text;
- end
- if t.attr.from == error_by then
- error_by = nil;
- end
- t:tag("error", {type = error_type, by = error_by}) --COMPAT: Some day xmlns:stanzas goes here
- :tag(condition, xmpp_stanzas_attr);
- if extra and condition == "gone" and type(extra.uri) == "string" then
- t:text(extra.uri);
- end
- t:up();
- if error_message then t:text_tag("text", error_message, xmpp_stanzas_attr); end
- if extra and is_stanza(extra.tag) then
- t:add_child(extra.tag);
- elseif extra and extra.namespace and extra.condition then
- t:tag(extra.condition, { xmlns = extra.namespace }):up();
- end
- return t; -- stanza ready for adding app-specific errors
+ t:add_error(error_type, condition, error_message, error_by);
+ t.last_add = { t[1] }; -- ready to add application-specific errors
+ return t;
end
local function presence(attr)
diff --git a/util/vcard.lua b/util/vcard.lua
deleted file mode 100644
index e311f73f..00000000
--- a/util/vcard.lua
+++ /dev/null
@@ -1,574 +0,0 @@
--- Copyright (C) 2011-2014 Kim Alvefur
---
--- This project is MIT/X11 licensed. Please see the
--- COPYING file in the source package for more information.
---
-
--- TODO
--- Fix folding.
-
-local st = require "util.stanza";
-local t_insert, t_concat = table.insert, table.concat;
-local type = type;
-local pairs, ipairs = pairs, ipairs;
-
-local from_text, to_text, from_xep54, to_xep54;
-
-local line_sep = "\n";
-
-local vCard_dtd; -- See end of file
-local vCard4_dtd;
-
-local function vCard_esc(s)
- return s:gsub("[,:;\\]", "\\%1"):gsub("\n","\\n");
-end
-
-local function vCard_unesc(s)
- return s:gsub("\\?[\\nt:;,]", {
- ["\\\\"] = "\\",
- ["\\n"] = "\n",
- ["\\r"] = "\r",
- ["\\t"] = "\t",
- ["\\:"] = ":", -- FIXME Shouldn't need to escape : in values, just params
- ["\\;"] = ";",
- ["\\,"] = ",",
- [":"] = "\29",
- [";"] = "\30",
- [","] = "\31",
- });
-end
-
-local function item_to_xep54(item)
- local t = st.stanza(item.name, { xmlns = "vcard-temp" });
-
- local prop_def = vCard_dtd[item.name];
- if prop_def == "text" then
- t:text(item[1]);
- elseif type(prop_def) == "table" then
- if prop_def.types and item.TYPE then
- if type(item.TYPE) == "table" then
- for _,v in pairs(prop_def.types) do
- for _,typ in pairs(item.TYPE) do
- if typ:upper() == v then
- t:tag(v):up();
- break;
- end
- end
- end
- else
- t:tag(item.TYPE:upper()):up();
- end
- end
-
- if prop_def.props then
- for _,prop in pairs(prop_def.props) do
- if item[prop] then
- for _, v in ipairs(item[prop]) do
- t:text_tag(prop, v);
- end
- end
- end
- end
-
- if prop_def.value then
- t:text_tag(prop_def.value, item[1]);
- elseif prop_def.values then
- local prop_def_values = prop_def.values;
- local repeat_last = prop_def_values.behaviour == "repeat-last" and prop_def_values[#prop_def_values];
- for i=1,#item do
- t:text_tag(prop_def.values[i] or repeat_last, item[i]);
- end
- end
- end
-
- return t;
-end
-
-local function vcard_to_xep54(vCard)
- local t = st.stanza("vCard", { xmlns = "vcard-temp" });
- for i=1,#vCard do
- t:add_child(item_to_xep54(vCard[i]));
- end
- return t;
-end
-
-function to_xep54(vCards)
- if not vCards[1] or vCards[1].name then
- return vcard_to_xep54(vCards)
- else
- local t = st.stanza("xCard", { xmlns = "vcard-temp" });
- for i=1,#vCards do
- t:add_child(vcard_to_xep54(vCards[i]));
- end
- return t;
- end
-end
-
-function from_text(data)
- data = data -- unfold and remove empty lines
- :gsub("\r\n","\n")
- :gsub("\n ", "")
- :gsub("\n\n+","\n");
- local vCards = {};
- local current;
- for line in data:gmatch("[^\n]+") do
- line = vCard_unesc(line);
- local name, params, value = line:match("^([-%a]+)(\30?[^\29]*)\29(.*)$");
- value = value:gsub("\29",":");
- if #params > 0 then
- local _params = {};
- for k,isval,v in params:gmatch("\30([^=]+)(=?)([^\30]*)") do
- k = k:upper();
- local _vt = {};
- for _p in v:gmatch("[^\31]+") do
- _vt[#_vt+1]=_p
- _vt[_p]=true;
- end
- if isval == "=" then
- _params[k]=_vt;
- else
- _params[k]=true;
- end
- end
- params = _params;
- end
- if name == "BEGIN" and value == "VCARD" then
- current = {};
- vCards[#vCards+1] = current;
- elseif name == "END" and value == "VCARD" then
- current = nil;
- elseif current and vCard_dtd[name] then
- local dtd = vCard_dtd[name];
- local item = { name = name };
- t_insert(current, item);
- local up = current;
- current = item;
- if dtd.types then
- for _, t in ipairs(dtd.types) do
- t = t:lower();
- if ( params.TYPE and params.TYPE[t] == true)
- or params[t] == true then
- current.TYPE=t;
- end
- end
- end
- if dtd.props then
- for _, p in ipairs(dtd.props) do
- if params[p] then
- if params[p] == true then
- current[p]=true;
- else
- for _, prop in ipairs(params[p]) do
- current[p]=prop;
- end
- end
- end
- end
- end
- if dtd == "text" or dtd.value then
- t_insert(current, value);
- elseif dtd.values then
- for p in ("\30"..value):gmatch("\30([^\30]*)") do
- t_insert(current, p);
- end
- end
- current = up;
- end
- end
- return vCards;
-end
-
-local function item_to_text(item)
- local value = {};
- for i=1,#item do
- value[i] = vCard_esc(item[i]);
- end
- value = t_concat(value, ";");
-
- local params = "";
- for k,v in pairs(item) do
- if type(k) == "string" and k ~= "name" then
- params = params .. (";%s=%s"):format(k, type(v) == "table" and t_concat(v,",") or v);
- end
- end
-
- return ("%s%s:%s"):format(item.name, params, value)
-end
-
-local function vcard_to_text(vcard)
- local t={};
- t_insert(t, "BEGIN:VCARD")
- for i=1,#vcard do
- t_insert(t, item_to_text(vcard[i]));
- end
- t_insert(t, "END:VCARD")
- return t_concat(t, line_sep);
-end
-
-function to_text(vCards)
- if vCards[1] and vCards[1].name then
- return vcard_to_text(vCards)
- else
- local t = {};
- for i=1,#vCards do
- t[i]=vcard_to_text(vCards[i]);
- end
- return t_concat(t, line_sep);
- end
-end
-
-local function from_xep54_item(item)
- local prop_name = item.name;
- local prop_def = vCard_dtd[prop_name];
-
- local prop = { name = prop_name };
-
- if prop_def == "text" then
- prop[1] = item:get_text();
- elseif type(prop_def) == "table" then
- if prop_def.value then --single item
- prop[1] = item:get_child_text(prop_def.value) or "";
- elseif prop_def.values then --array
- local value_names = prop_def.values;
- if value_names.behaviour == "repeat-last" then
- for i=1,#item.tags do
- t_insert(prop, item.tags[i]:get_text() or "");
- end
- else
- for i=1,#value_names do
- t_insert(prop, item:get_child_text(value_names[i]) or "");
- end
- end
- elseif prop_def.names then
- local names = prop_def.names;
- for i=1,#names do
- if item:get_child(names[i]) then
- prop[1] = names[i];
- break;
- end
- end
- end
-
- if prop_def.props_verbatim then
- for k,v in pairs(prop_def.props_verbatim) do
- prop[k] = v;
- end
- end
-
- if prop_def.types then
- local types = prop_def.types;
- prop.TYPE = {};
- for i=1,#types do
- if item:get_child(types[i]) then
- t_insert(prop.TYPE, types[i]:lower());
- end
- end
- if #prop.TYPE == 0 then
- prop.TYPE = nil;
- end
- end
-
- -- A key-value pair, within a key-value pair?
- if prop_def.props then
- local params = prop_def.props;
- for i=1,#params do
- local name = params[i]
- local data = item:get_child_text(name);
- if data then
- prop[name] = prop[name] or {};
- t_insert(prop[name], data);
- end
- end
- end
- else
- return nil
- end
-
- return prop;
-end
-
-local function from_xep54_vCard(vCard)
- local tags = vCard.tags;
- local t = {};
- for i=1,#tags do
- t_insert(t, from_xep54_item(tags[i]));
- end
- return t
-end
-
-function from_xep54(vCard)
- if vCard.attr.xmlns ~= "vcard-temp" then
- return nil, "wrong-xmlns";
- end
- if vCard.name == "xCard" then -- A collection of vCards
- local t = {};
- local vCards = vCard.tags;
- for i=1,#vCards do
- t[i] = from_xep54_vCard(vCards[i]);
- end
- return t
- elseif vCard.name == "vCard" then -- A single vCard
- return from_xep54_vCard(vCard)
- end
-end
-
-local vcard4 = { }
-
-function vcard4:text(node, params, value) -- luacheck: ignore 212/params
- self:tag(node:lower())
- -- FIXME params
- if type(value) == "string" then
- self:text_tag("text", value);
- elseif vcard4[node] then
- vcard4[node](value);
- end
- self:up();
-end
-
-function vcard4.N(value)
- for i, k in ipairs(vCard_dtd.N.values) do
- value:text_tag(k, value[i]);
- end
-end
-
-local xmlns_vcard4 = "urn:ietf:params:xml:ns:vcard-4.0"
-
-local function item_to_vcard4(item)
- local typ = item.name:lower();
- local t = st.stanza(typ, { xmlns = xmlns_vcard4 });
-
- local prop_def = vCard4_dtd[typ];
- if prop_def == "text" then
- t:text_tag("text", item[1]);
- elseif prop_def == "uri" then
- if item.ENCODING and item.ENCODING[1] == 'b' then
- t:text_tag("uri", "data:;base64," .. item[1]);
- else
- t:text_tag("uri", item[1]);
- end
- elseif type(prop_def) == "table" then
- if prop_def.values then
- for i, v in ipairs(prop_def.values) do
- t:text_tag(v:lower(), item[i]);
- end
- else
- t:tag("unsupported",{xmlns="http://zash.se/protocol/vcardlib"})
- end
- else
- t:tag("unsupported",{xmlns="http://zash.se/protocol/vcardlib"})
- end
- return t;
-end
-
-local function vcard_to_vcard4xml(vCard)
- local t = st.stanza("vcard", { xmlns = xmlns_vcard4 });
- for i=1,#vCard do
- t:add_child(item_to_vcard4(vCard[i]));
- end
- return t;
-end
-
-local function vcards_to_vcard4xml(vCards)
- if not vCards[1] or vCards[1].name then
- return vcard_to_vcard4xml(vCards)
- else
- local t = st.stanza("vcards", { xmlns = xmlns_vcard4 });
- for i=1,#vCards do
- t:add_child(vcard_to_vcard4xml(vCards[i]));
- end
- return t;
- end
-end
-
--- This was adapted from http://xmpp.org/extensions/xep-0054.html#dtd
-vCard_dtd = {
- VERSION = "text", --MUST be 3.0, so parsing is redundant
- FN = "text",
- N = {
- values = {
- "FAMILY",
- "GIVEN",
- "MIDDLE",
- "PREFIX",
- "SUFFIX",
- },
- },
- NICKNAME = "text",
- PHOTO = {
- props_verbatim = { ENCODING = { "b" } },
- props = { "TYPE" },
- value = "BINVAL", --{ "EXTVAL", },
- },
- BDAY = "text",
- ADR = {
- types = {
- "HOME",
- "WORK",
- "POSTAL",
- "PARCEL",
- "DOM",
- "INTL",
- "PREF",
- },
- values = {
- "POBOX",
- "EXTADD",
- "STREET",
- "LOCALITY",
- "REGION",
- "PCODE",
- "CTRY",
- }
- },
- LABEL = {
- types = {
- "HOME",
- "WORK",
- "POSTAL",
- "PARCEL",
- "DOM",
- "INTL",
- "PREF",
- },
- value = "LINE",
- },
- TEL = {
- types = {
- "HOME",
- "WORK",
- "VOICE",
- "FAX",
- "PAGER",
- "MSG",
- "CELL",
- "VIDEO",
- "BBS",
- "MODEM",
- "ISDN",
- "PCS",
- "PREF",
- },
- value = "NUMBER",
- },
- EMAIL = {
- types = {
- "HOME",
- "WORK",
- "INTERNET",
- "PREF",
- "X400",
- },
- value = "USERID",
- },
- JABBERID = "text",
- MAILER = "text",
- TZ = "text",
- GEO = {
- values = {
- "LAT",
- "LON",
- },
- },
- TITLE = "text",
- ROLE = "text",
- LOGO = "copy of PHOTO",
- AGENT = "text",
- ORG = {
- values = {
- behaviour = "repeat-last",
- "ORGNAME",
- "ORGUNIT",
- }
- },
- CATEGORIES = {
- values = "KEYWORD",
- },
- NOTE = "text",
- PRODID = "text",
- REV = "text",
- SORTSTRING = "text",
- SOUND = "copy of PHOTO",
- UID = "text",
- URL = "text",
- CLASS = {
- names = { -- The item.name is the value if it's one of these.
- "PUBLIC",
- "PRIVATE",
- "CONFIDENTIAL",
- },
- },
- KEY = {
- props = { "TYPE" },
- value = "CRED",
- },
- DESC = "text",
-};
-vCard_dtd.LOGO = vCard_dtd.PHOTO;
-vCard_dtd.SOUND = vCard_dtd.PHOTO;
-
-vCard4_dtd = {
- source = "uri",
- kind = "text",
- xml = "text",
- fn = "text",
- n = {
- values = {
- "family",
- "given",
- "middle",
- "prefix",
- "suffix",
- },
- },
- nickname = "text",
- photo = "uri",
- bday = "date-and-or-time",
- anniversary = "date-and-or-time",
- gender = "text",
- adr = {
- values = {
- "pobox",
- "ext",
- "street",
- "locality",
- "region",
- "code",
- "country",
- }
- },
- tel = "text",
- email = "text",
- impp = "uri",
- lang = "language-tag",
- tz = "text",
- geo = "uri",
- title = "text",
- role = "text",
- logo = "uri",
- org = "text",
- member = "uri",
- related = "uri",
- categories = "text",
- note = "text",
- prodid = "text",
- rev = "timestamp",
- sound = "uri",
- uid = "uri",
- clientpidmap = "number, uuid",
- url = "uri",
- version = "text",
- key = "uri",
- fburl = "uri",
- caladruri = "uri",
- caluri = "uri",
-};
-
-return {
- from_text = from_text;
- to_text = to_text;
-
- from_xep54 = from_xep54;
- to_xep54 = to_xep54;
-
- to_vcard4 = vcards_to_vcard4xml;
-};
diff --git a/util/watchdog.lua b/util/watchdog.lua
index 516e60e4..407028a5 100644
--- a/util/watchdog.lua
+++ b/util/watchdog.lua
@@ -1,6 +1,5 @@
local timer = require "util.timer";
local setmetatable = setmetatable;
-local os_time = os.time;
local _ENV = nil;
-- luacheck: std none
@@ -9,27 +8,35 @@ local watchdog_methods = {};
local watchdog_mt = { __index = watchdog_methods };
local function new(timeout, callback)
- local watchdog = setmetatable({ timeout = timeout, last_reset = os_time(), callback = callback }, watchdog_mt);
- timer.add_task(timeout+1, function (current_time)
- local last_reset = watchdog.last_reset;
- if not last_reset then
- return;
- end
- local time_left = (last_reset + timeout) - current_time;
- if time_left < 0 then
- return watchdog:callback();
- end
- return time_left + 1;
- end);
+ local watchdog = setmetatable({
+ timeout = timeout;
+ callback = callback;
+ timer_id = nil;
+ }, watchdog_mt);
+
+ watchdog:reset(); -- Kick things off
+
return watchdog;
end
-function watchdog_methods:reset()
- self.last_reset = os_time();
+function watchdog_methods:reset(new_timeout)
+ if new_timeout then
+ self.timeout = new_timeout;
+ end
+ if self.timer_id then
+ timer.reschedule(self.timer_id, self.timeout+1);
+ else
+ self.timer_id = timer.add_task(self.timeout+1, function ()
+ return self:callback();
+ end);
+ end
end
function watchdog_methods:cancel()
- self.last_reset = nil;
+ if self.timer_id then
+ timer.stop(self.timer_id);
+ self.timer_id = nil;
+ end
end
return {
diff --git a/util/x509.lua b/util/x509.lua
index 76b50076..51ca3c96 100644
--- a/util/x509.lua
+++ b/util/x509.lua
@@ -11,12 +11,12 @@
-- IDN libraries complicate that.
--- [TLS-CERTS] - http://tools.ietf.org/html/rfc6125
--- [XMPP-CORE] - http://tools.ietf.org/html/rfc6120
--- [SRV-ID] - http://tools.ietf.org/html/rfc4985
--- [IDNA] - http://tools.ietf.org/html/rfc5890
--- [LDAP] - http://tools.ietf.org/html/rfc4519
--- [PKIX] - http://tools.ietf.org/html/rfc5280
+-- [TLS-CERTS] - https://www.rfc-editor.org/rfc/rfc6125.html
+-- [XMPP-CORE] - https://www.rfc-editor.org/rfc/rfc6120.html
+-- [SRV-ID] - https://www.rfc-editor.org/rfc/rfc4985.html
+-- [IDNA] - https://www.rfc-editor.org/rfc/rfc5890.html
+-- [LDAP] - https://www.rfc-editor.org/rfc/rfc4519.html
+-- [PKIX] - https://www.rfc-editor.org/rfc/rfc5280.html
local nameprep = require "util.encodings".stringprep.nameprep;
local idna_to_ascii = require "util.encodings".idna.to_ascii;