aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGES2
-rw-r--r--doc/doap.xml30
-rw-r--r--net/http/server.lua2
-rw-r--r--net/server_epoll.lua108
-rw-r--r--plugins/mod_admin_adhoc.lua20
-rw-r--r--plugins/mod_admin_shell.lua21
-rw-r--r--plugins/mod_authz_internal.lua2
-rw-r--r--plugins/mod_bookmarks.lua15
-rw-r--r--plugins/mod_csi.lua4
-rw-r--r--plugins/mod_http_file_share.lua4
-rw-r--r--plugins/mod_invites_adhoc.lua30
-rw-r--r--plugins/mod_pubsub/mod_pubsub.lua23
-rw-r--r--plugins/mod_pubsub/pubsub.lib.lua56
-rw-r--r--plugins/mod_s2s.lua30
-rw-r--r--plugins/mod_smacks.lua12
-rw-r--r--plugins/muc/hats.lib.lua2
-rw-r--r--plugins/muc/muc.lib.lua10
-rw-r--r--spec/scansion/bookmarks2.scs181
-rw-r--r--spec/util_crypto_spec.lua21
-rw-r--r--spec/util_error_spec.lua14
-rw-r--r--spec/util_pubsub_spec.lua12
-rw-r--r--spec/util_queue_spec.lua19
-rw-r--r--teal-src/prosody/util/xtemplate.tl8
-rw-r--r--util-src/crypto.c116
-rw-r--r--util/dnsregistry.lua7
-rw-r--r--util/pubsub.lua26
-rw-r--r--util/xtemplate.lua6
27 files changed, 622 insertions, 159 deletions
diff --git a/CHANGES b/CHANGES
index fbeec13c..4b3f0b6f 100644
--- a/CHANGES
+++ b/CHANGES
@@ -6,6 +6,7 @@ TRUNK
### Administration
- Add 'watch log' command to follow live debug logs at runtime (even if disabled)
+- mod_announce: Add shell commands to send messages to all users, online users, or limited by roles
### Networking
@@ -73,6 +74,7 @@ TRUNK
- Support for Type=notify and notify-reload systemd service type added
- Support for the roster *group* access_model in mod_pep
- Support for systemd socket activation in server_epoll
+- mod_invites_adhoc gained a command for creating password resets
## Removed
diff --git a/doc/doap.xml b/doc/doap.xml
index 62e4063c..cf150c36 100644
--- a/doc/doap.xml
+++ b/doc/doap.xml
@@ -72,7 +72,7 @@
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0004.html"/>
- <xmpp:version>2.13.0</xmpp:version>
+ <xmpp:version>2.13.1</xmpp:version>
<xmpp:since>0.4.0</xmpp:since>
<xmpp:status>partial</xmpp:status>
<xmpp:note>no support for multiple items (reported tag)</xmpp:note>
@@ -107,7 +107,7 @@
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0030.html"/>
- <xmpp:version>2.5rc3</xmpp:version>
+ <xmpp:version>2.5.0</xmpp:version>
<xmpp:since>0.10.0</xmpp:since>
<xmpp:status>complete</xmpp:status>
</xmpp:SupportedXep>
@@ -274,13 +274,13 @@
<xmpp:version>1.0</xmpp:version>
<xmpp:since>0.9.0</xmpp:since>
<xmpp:status>complete</xmpp:status>
- <xmpp:note>util.jid.(un)escape, missing rejection of \20 at start or end per xep version 1.1</xmpp:note>
+ <xmpp:note>util.jid.(un)escape, missing rejection of \20 at start or end per xep version 1.1. Missing PRECIS for version 1.1.1.</xmpp:note>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0107.html"/>
- <xmpp:version>1.2.1</xmpp:version>
+ <xmpp:version>1.2.2</xmpp:version>
<xmpp:status>complete</xmpp:status>
<xmpp:note>via XEP-0163</xmpp:note>
</xmpp:SupportedXep>
@@ -353,7 +353,7 @@
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0133.html"/>
- <xmpp:version>1.2</xmpp:version>
+ <xmpp:version>1.3.1</xmpp:version>
<xmpp:since>0.7.0</xmpp:since>
<xmpp:status>partial</xmpp:status>
<xmpp:note>mod_admin_adhoc, missing some commands</xmpp:note>
@@ -372,7 +372,7 @@
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0153.html"/>
- <xmpp:version>1.1</xmpp:version>
+ <xmpp:version>1.1.1</xmpp:version>
<xmpp:since>0.11.0</xmpp:since>
<xmpp:status>complete</xmpp:status>
<xmpp:note>via XEP-0398</xmpp:note>
@@ -508,7 +508,7 @@
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0198.html"/>
- <xmpp:version>1.6</xmpp:version>
+ <xmpp:version>1.6.2</xmpp:version>
<xmpp:status>complete</xmpp:status>
<xmpp:since>0.12.0</xmpp:since>
<xmpp:note>mod_smacks</xmpp:note>
@@ -594,7 +594,7 @@
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0223.html"/>
- <xmpp:version>1.1</xmpp:version>
+ <xmpp:version>1.1.1</xmpp:version>
<xmpp:since>0.11.0</xmpp:since>
<xmpp:status>complete</xmpp:status>
<xmpp:note>mod_pep</xmpp:note>
@@ -655,7 +655,7 @@
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0292.html"/>
- <xmpp:version>0.11</xmpp:version>
+ <xmpp:version>0.12.0</xmpp:version>
<xmpp:status>complete</xmpp:status>
<xmpp:since>0.11.0</xmpp:since>
<xmpp:note>mod_vcard4, mod_vcard_legacy</xmpp:note>
@@ -690,7 +690,7 @@
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0313.html"/>
- <xmpp:version>1.1.0</xmpp:version>
+ <xmpp:version>1.1.2</xmpp:version>
<xmpp:status>complete</xmpp:status>
<xmpp:since>0.10.0</xmpp:since>
<xmpp:note>mod_mam, mod_muc_mam</xmpp:note>
@@ -787,7 +787,7 @@
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0398.html"/>
- <xmpp:version>0.2.1</xmpp:version>
+ <xmpp:version>1.0.0</xmpp:version>
<xmpp:since>0.11.0</xmpp:since>
<xmpp:status>complete</xmpp:status>
<xmpp:note>mod_vcard_legacy</xmpp:note>
@@ -804,7 +804,7 @@
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0402.html"/>
- <xmpp:version>1.1.3</xmpp:version>
+ <xmpp:version>1.2.0</xmpp:version>
<xmpp:since>0.12.0</xmpp:since>
<xmpp:status>complete</xmpp:status>
<xmpp:note>mod_bookmarks</xmpp:note>
@@ -831,7 +831,7 @@
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0421.html"/>
- <xmpp:version>0.1.0</xmpp:version>
+ <xmpp:version>0.2.0</xmpp:version>
<xmpp:since>0.12.0</xmpp:since>
<xmpp:status>complete</xmpp:status>
<xmpp:note>mod_muc</xmpp:note>
@@ -847,7 +847,7 @@
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0440.html"/>
- <xmpp:version>0.4.0</xmpp:version>
+ <xmpp:version>0.4.2</xmpp:version>
<xmpp:since>trunk</xmpp:since>
<xmpp:status>complete</xmpp:status>
</xmpp:SupportedXep>
@@ -871,7 +871,7 @@
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0478.html"/>
- <xmpp:version>0.1.0</xmpp:version>
+ <xmpp:version>0.2.0</xmpp:version>
<xmpp:since>trunk</xmpp:since>
</xmpp:SupportedXep>
</implements>
diff --git a/net/http/server.lua b/net/http/server.lua
index edc6d879..1ef42b2e 100644
--- a/net/http/server.lua
+++ b/net/http/server.lua
@@ -206,7 +206,7 @@ local function handle_result(request, response, result)
end
elseif result_type == "string" then
body = result;
- elseif errors.is_err(result) then
+ elseif errors.is_error(result) then
response.status_code = result.code or 500;
body = events.fire_event("http-error", { request = request, response = response, code = result.code or 500, error = result });
elseif promise.is_promise(result) then
diff --git a/net/server_epoll.lua b/net/server_epoll.lua
index 6c110241..24678842 100644
--- a/net/server_epoll.lua
+++ b/net/server_epoll.lua
@@ -6,8 +6,6 @@
--
-local t_insert = table.insert;
-local t_concat = table.concat;
local setmetatable = setmetatable;
local pcall = pcall;
local type = type;
@@ -22,6 +20,7 @@ local realtime = require "prosody.util.time".now;
local monotonic = require "prosody.util.time".monotonic;
local indexedbheap = require "prosody.util.indexedbheap";
local createtable = require "prosody.util.table".create;
+local dbuffer = require "prosody.util.dbuffer";
local inet = require "prosody.util.net";
local inet_pton = inet.pton;
local _SOCKETINVALID = socket._SOCKETINVALID or -1;
@@ -94,6 +93,15 @@ local default_config = { __index = {
-- Size of chunks to read from sockets
read_size = 8192;
+ -- Maximum size of send buffer, after which additional data is rejected
+ max_send_buffer_size = 32*1024*1024;
+
+ -- How many chunks (immutable strings) to keep in the send buffer
+ send_buffer_chunks = nil;
+
+ -- Maximum amount of data to send at once (to the TCP buffers), default based on /proc/sys/net/ipv4/tcp_wmem
+ max_send_chunk = 4*1024*1024;
+
-- Timeout used during between steps in TLS handshakes
ssl_handshake_timeout = 60;
@@ -533,26 +541,21 @@ function interface:onwritable()
self:onconnect();
if not self.conn then return nil, "no-conn"; end -- could have been closed in onconnect
self:on("predrain");
- local buffer = self.writebuffer;
- local data = buffer or "";
- if type(buffer) == "table" then
- if buffer[3] then
- data = t_concat(data);
- elseif buffer[2] then
- data = buffer[1] .. buffer[2];
- else
- data = buffer[1] or "";
- end
- end
+ local buffer = self.writebuffer or "";
+ -- Naming things ... s/data/slice/ ?
+ local data = buffer:sub(1, cfg.max_send_chunk);
local ok, err, partial = self.conn:send(data);
self._writable = ok;
- if ok then
+ if ok and #data < #buffer then
+ -- Sent the whole 'data' but there's more in the buffer
+ ok, err, partial = nil, "timeout", ok;
+ end
+ self:debug("Sent %d out of %d buffered bytes", ok and #data or partial or 0, #buffer);
+ if ok then -- all the data we had was sent successfully
self:set(nil, false);
if cfg.keep_buffers and type(buffer) == "table" then
- for i = #buffer, 1, -1 do
- buffer[i] = nil;
- end
- else
+ buffer:discard(ok);
+ else -- string or don't keep buffers
self.writebuffer = nil;
end
self._writing = nil;
@@ -560,14 +563,10 @@ function interface:onwritable()
self:ondrain(); -- Be aware of writes in ondrain
return ok;
elseif partial then
- self:debug("Sent %d out of %d buffered bytes", partial, #data);
- if cfg.keep_buffers and type(buffer) == "table" then
- buffer[1] = data:sub(partial+1);
- for i = #buffer, 2, -1 do
- buffer[i] = nil;
- end
+ if type(buffer) == "table" then
+ buffer:discard(partial);
else
- self.writebuffer = data:sub(partial+1);
+ self.writebuffer = data:sub(partial + 1);
end
self:set(nil, true);
self:setwritetimeout();
@@ -595,13 +594,51 @@ end
-- Add data to write buffer and set flag for wanting to write
function interface:write(data)
local buffer = self.writebuffer;
- if type(buffer) == "table" then
- t_insert(buffer, data);
- elseif type(buffer) == "string" then
- self:noise("Allocating buffer!")
- self.writebuffer = { buffer, data };
- elseif buffer == nil then
+ -- (nil) -> save string
+ -- (string) -> convert to buffer (3 tables!)
+ -- (buffer) -> write to buffer
+ if not buffer then
self.writebuffer = data;
+ elseif type(buffer) == "string" then
+ local prev_buffer = buffer;
+ buffer = dbuffer.new(cfg.max_send_buffer_size, cfg.send_buffer_chunks);
+ self.writebuffer = buffer;
+ if prev_buffer then
+ -- TODO refactor, there's 3 copies of these lines
+ if not buffer:write(prev_buffer) then
+ if self._write_lock then
+ return false;
+ end
+ -- Try to flush buffer to make room
+ self:onwritable();
+ if not buffer:write(prev_buffer) then
+ self:on("disconnect", "no space left in buffer");
+ self:destroy();
+ return false;
+ end
+ end
+ end
+ if not buffer:write(data) then
+ if self._write_lock then
+ return false;
+ end
+ self:onwritable();
+ if not buffer:write(data) then
+ self:on("disconnect", "no space left in buffer");
+ self:destroy();
+ return false;
+ end
+ end
+ elseif not buffer:write(data) then
+ if self._write_lock then
+ return false;
+ end
+ self:onwritable();
+ if not buffer:write(data) then
+ self:on("disconnect", "no space left in buffer");
+ self:destroy();
+ return false;
+ end
end
if not self._write_lock and not self._writing then
if self._writable and cfg.opportunistic_writes and not self._opportunistic_write then
@@ -619,7 +656,7 @@ interface.send = interface.write;
-- Close, possibly after writing is done
function interface:close()
- if self._connected and self.writebuffer and (self.writebuffer[1] or type(self.writebuffer) == "string") then
+ if self.writebuffer and #self.writebuffer ~= 0 then
self._connected = false;
self:set(false, true); -- Flush final buffer contents
self:setreadtimeout(false);
@@ -701,7 +738,7 @@ end
function interface:starttls(tls_ctx)
if tls_ctx then self.tls_ctx = tls_ctx; end
self.starttls = false;
- if self.writebuffer and (self.writebuffer[1] or type(self.writebuffer) == "string") then
+ if self.writebuffer and #self.writebuffer ~= 0 then
self:debug("Start TLS after write");
self.ondrain = interface.starttls;
self:set(nil, true); -- make sure wantwrite is set
@@ -935,7 +972,10 @@ function interface:resume_writes()
end
self:noise("Resume writes");
self._write_lock = nil;
- if self.writebuffer and (self.writebuffer[1] or type(self.writebuffer) == "string") then
+ if self.writebuffer and #self.writebuffer ~= 0 then
+ if cfg.opportunistic_writes then
+ return self:onwritable();
+ end
self:setwritetimeout();
self:set(nil, true);
end
diff --git a/plugins/mod_admin_adhoc.lua b/plugins/mod_admin_adhoc.lua
index ee26b7e5..ca84f975 100644
--- a/plugins/mod_admin_adhoc.lua
+++ b/plugins/mod_admin_adhoc.lua
@@ -592,15 +592,15 @@ end, function(fields, err, data)
return generate_error_message(err);
end
local ok_list, err_list = {}, {};
- for _, module in ipairs(fields.modules) do
- local ok, err = modulemanager.reload(module_host, module);
+ for _, module_ in ipairs(fields.modules) do
+ local ok, err = modulemanager.reload(module_host, module_);
if ok then
- ok_list[#ok_list + 1] = module;
+ ok_list[#ok_list + 1] = module_;
else
- err_list[#err_list + 1] = module .. "(Error: " .. tostring(err) .. ")";
+ err_list[#err_list + 1] = module_ .. "(Error: " .. tostring(err) .. ")";
end
+ module:log("info", "mod_%s reloaded by %s", module_, jid.bare(data.from));
end
- module:log("info", "mod_%s reloaded by %s", fields.module, jid.bare(data.from));
local info = (#ok_list > 0 and ("The following modules were successfully reloaded on host "..module_host..":\n"..t_concat(ok_list, "\n")) or "")
.. ((#ok_list > 0 and #err_list > 0) and "\n" or "") ..
(#err_list > 0 and ("Failed to reload the following modules on host "..module_host..":\n"..t_concat(err_list, "\n")) or "");
@@ -742,15 +742,15 @@ end, function(fields, err, data)
return generate_error_message(err);
end
local ok_list, err_list = {}, {};
- for _, module in ipairs(fields.modules) do
- local ok, err = modulemanager.unload(module_host, module);
+ for _, module_ in ipairs(fields.modules) do
+ local ok, err = modulemanager.unload(module_host, module_);
if ok then
- ok_list[#ok_list + 1] = module;
+ ok_list[#ok_list + 1] = module_;
else
- err_list[#err_list + 1] = module .. "(Error: " .. tostring(err) .. ")";
+ err_list[#err_list + 1] = module_ .. "(Error: " .. tostring(err) .. ")";
end
+ module:log("info", "mod_%s unloaded by %s", module_, jid.bare(data.from));
end
- module:log("info", "mod_%s unloaded by %s", fields.module, jid.bare(data.from));
local info = (#ok_list > 0 and ("The following modules were successfully unloaded on host "..module_host..":\n"..t_concat(ok_list, "\n")) or "")
.. ((#ok_list > 0 and #err_list > 0) and "\n" or "") ..
(#err_list > 0 and ("Failed to unload the following modules on host "..module_host..":\n"..t_concat(err_list, "\n")) or "");
diff --git a/plugins/mod_admin_shell.lua b/plugins/mod_admin_shell.lua
index e6b44f00..0b8d3c43 100644
--- a/plugins/mod_admin_shell.lua
+++ b/plugins/mod_admin_shell.lua
@@ -631,6 +631,7 @@ describe_command [[module:load(module, host) - Load the specified module on the
function def_env.module:load(name, hosts)
hosts = get_hosts_with_module(hosts);
+ local already_loaded = set.new();
-- Load the module for each host
local ok, err, count, mod = true, nil, 0;
for host in hosts do
@@ -655,10 +656,18 @@ function def_env.module:load(name, hosts)
self.session.print("Note: Module will not be loaded after restart unless enabled in configuration");
end
end
+ else
+ already_loaded:add(host);
end
end
- return ok, (ok and "Module loaded onto "..count.." host"..(count ~= 1 and "s" or "")) or ("Last error: "..tostring(err));
+ if not ok then
+ return ok, "Last error: "..tostring(err);
+ end
+ if already_loaded == hosts then
+ return ok, "Module already loaded";
+ end
+ return ok, "Module loaded onto "..count.." host"..(count ~= 1 and "s" or "");
end
describe_command [[module:unload(module, host) - The same, but just unloads the module from memory]]
@@ -1770,7 +1779,11 @@ function def_env.user:setrole(jid, host, new_role)
elseif prosody.hosts[userhost] and not um.user_exists(username, userhost) then
return nil, "No such user";
end
- return um.set_user_role(username, host, new_role);
+ if userhost == host then
+ return um.set_user_role(username, userhost, new_role);
+ else
+ return um.set_jid_role(jid, host, new_role);
+ end
end
describe_command [[user:addrole(jid, host, role) - Add a secondary role to a user]]
@@ -1781,6 +1794,8 @@ function def_env.user:addrole(jid, host, new_role)
return nil, "No such host: "..host;
elseif prosody.hosts[userhost] and not um.user_exists(username, userhost) then
return nil, "No such user";
+ elseif userhost ~= host then
+ return nil, "Can't add roles outside users own host"
end
return um.add_user_secondary_role(username, host, new_role);
end
@@ -1793,6 +1808,8 @@ function def_env.user:delrole(jid, host, role_name)
return nil, "No such host: "..host;
elseif prosody.hosts[userhost] and not um.user_exists(username, userhost) then
return nil, "No such user";
+ elseif userhost ~= host then
+ return nil, "Can't remove roles outside users own host"
end
return um.remove_user_secondary_role(username, host, role_name);
end
diff --git a/plugins/mod_authz_internal.lua b/plugins/mod_authz_internal.lua
index 96324734..07091a04 100644
--- a/plugins/mod_authz_internal.lua
+++ b/plugins/mod_authz_internal.lua
@@ -265,7 +265,7 @@ function get_jid_role(jid)
end
function set_jid_role(jid, role_name) -- luacheck: ignore 212
- return false;
+ return false, "not-implemented";
end
function get_jids_with_role(role_name)
diff --git a/plugins/mod_bookmarks.lua b/plugins/mod_bookmarks.lua
index be665d0f..e6e74f56 100644
--- a/plugins/mod_bookmarks.lua
+++ b/plugins/mod_bookmarks.lua
@@ -167,10 +167,15 @@ local function publish_to_pep(jid, bookmarks, synchronise)
if synchronise then
-- If we set zero legacy bookmarks, purge the bookmarks 2 node.
module:log("debug", "No bookmark in the set, purging instead.");
- return service:purge(namespace, jid, true);
- else
- return true;
+ local ok, err = service:purge(namespace, jid, true);
+ -- It's okay if no node exists when purging, user has
+ -- no bookmarks anyway.
+ if not ok and err ~= "item-not-found" then
+ module:log("error", "Failed to clear items from bookmarks 2 node: %s", err);
+ return ok, err;
+ end
end
+ return true;
end
-- Retrieve the current bookmarks2.
@@ -309,7 +314,7 @@ local function on_publish_legacy_pep(event)
local ok, err = publish_to_pep(session.full_jid, bookmarks, true);
if not ok then
- module:log("error", "Failed to publish to PEP bookmarks for %s@%s: %s", session.username, session.host, err);
+ module:log("error", "Failed to sync legacy bookmarks to PEP for %s@%s: %s", session.username, session.host, err);
session.send(st.error_reply(stanza, "cancel", "internal-server-error", "Failed to store bookmarks to PEP"));
return true;
end
@@ -335,7 +340,7 @@ local function on_publish_private_xml(event)
local ok, err = publish_to_pep(session.full_jid, bookmarks, true);
if not ok then
- module:log("error", "Failed to publish to PEP bookmarks for %s@%s: %s", session.username, session.host, err);
+ module:log("error", "Failed to sync private XML bookmarks to PEP for %s@%s: %s", session.username, session.host, err);
session.send(st.error_reply(stanza, "cancel", "internal-server-error", "Failed to store bookmarks to PEP"));
return true;
end
diff --git a/plugins/mod_csi.lua b/plugins/mod_csi.lua
index 73c081b1..76a5afd4 100644
--- a/plugins/mod_csi.lua
+++ b/plugins/mod_csi.lua
@@ -34,9 +34,9 @@ module:hook_global("stats-update", function()
if session.state == "inactive" then
inactive = inactive + 1;
elseif session.state == "active" then
- inactive = inactive + 1;
+ active = active + 1;
elseif session.state == "flushing" then
- inactive = inactive + 1;
+ flushing = flushing + 1;
end
end
end
diff --git a/plugins/mod_http_file_share.lua b/plugins/mod_http_file_share.lua
index cfc647d4..48972067 100644
--- a/plugins/mod_http_file_share.lua
+++ b/plugins/mod_http_file_share.lua
@@ -79,12 +79,12 @@ local measure_upload_cache_size = module:measure("upload_cache", "amount");
local measure_quota_cache_size = module:measure("quota_cache", "amount");
local measure_total_storage_usage = module:measure("total_storage", "amount", { unit = "bytes" });
-module:on_ready(function ()
+do
local total, err = persist_stats:get(nil, "total");
if not err then
total_storage_usage = tonumber(total) or 0;
end
-end)
+end
module:hook_global("stats-update", function ()
measure_upload_cache_size(upload_cache:count());
diff --git a/plugins/mod_invites_adhoc.lua b/plugins/mod_invites_adhoc.lua
index c9954d8c..3ef4116d 100644
--- a/plugins/mod_invites_adhoc.lua
+++ b/plugins/mod_invites_adhoc.lua
@@ -2,6 +2,7 @@
local dataforms = require "prosody.util.dataforms";
local datetime = require "prosody.util.datetime";
local split_jid = require "prosody.util.jid".split;
+local adhocutil = require "prosody.util.adhoc";
local new_adhoc = module:require("adhoc").new;
@@ -98,3 +99,32 @@ module:provides("adhoc", new_adhoc("Create new account invite", "urn:xmpp:invite
};
};
end, "admin"));
+
+local password_reset_form = dataforms.new({
+ title = "Generate Password Reset Invite";
+ {
+ name = "accountjid";
+ type = "jid-single";
+ required = true;
+ label = "The XMPP ID for the account to generate a password reset invite for";
+ };
+});
+
+module:provides("adhoc", new_adhoc("Create password reset invite", "xmpp:prosody.im/mod_invites_adhoc#password-reset",
+ adhocutil.new_simple_form(password_reset_form,
+ function (fields, err)
+ if err then return { status = "completed"; error = { message = "Fill in the form correctly" } }; end
+ local username = split_jid(fields.accountjid);
+ local invite = invites.create_account_reset(username);
+ return {
+ status = "completed";
+ result = {
+ layout = invite_result_form;
+ values = {
+ uri = invite.uri;
+ url = invite.landing_page;
+ expire = datetime.datetime(invite.expires);
+ };
+ };
+ };
+ end), "admin"));
diff --git a/plugins/mod_pubsub/mod_pubsub.lua b/plugins/mod_pubsub/mod_pubsub.lua
index 4f83088a..c17d9e63 100644
--- a/plugins/mod_pubsub/mod_pubsub.lua
+++ b/plugins/mod_pubsub/mod_pubsub.lua
@@ -190,10 +190,22 @@ module:hook("host-disco-items", function (event)
end);
local admin_aff = module:get_option_enum("default_admin_affiliation", "owner", "publisher", "member", "outcast", "none");
+
module:default_permission("prosody:admin", ":service-admin");
-local function get_affiliation(jid)
+module:default_permission("prosody:admin", ":create-node");
+
+local function get_affiliation(jid, _, action)
local bare_jid = jid_bare(jid);
- if bare_jid == module.host or module:may(":service-admin", bare_jid) then
+ if bare_jid == module.host then
+ -- The host itself (i.e. local modules) is treated as an admin.
+ -- Check this first as to avoid sendig a host JID to :may()
+ return admin_aff;
+ end
+ if action == "create" and module:may(":create-node", bare_jid) then
+ -- Only one affiliation is allowed to create nodes by default
+ return "owner";
+ end
+ if module:may(":service-admin", bare_jid) then
return admin_aff;
end
end
@@ -244,6 +256,13 @@ function module.load()
broadcaster = simple_broadcast;
itemcheck = is_item_stanza;
check_node_config = check_node_config;
+ metadata_subset = {
+ "title";
+ "description";
+ "payload_type";
+ "access_model";
+ "publish_model";
+ };
get_affiliation = get_affiliation;
jid = module.host;
diff --git a/plugins/mod_pubsub/pubsub.lib.lua b/plugins/mod_pubsub/pubsub.lib.lua
index 8ae0a896..f4d44f36 100644
--- a/plugins/mod_pubsub/pubsub.lib.lua
+++ b/plugins/mod_pubsub/pubsub.lib.lua
@@ -1,4 +1,3 @@
-local t_unpack = table.unpack;
local time_now = os.time;
local jid_prep = require "prosody.util.jid".prep;
@@ -18,7 +17,7 @@ local _M = {};
local handlers = {};
_M.handlers = handlers;
-local pubsub_errors = {
+local pubsub_errors = errors.init("pubsub", xmlns_pubsub_errors, {
["conflict"] = { "cancel", "conflict" };
["invalid-jid"] = { "modify", "bad-request", nil, "invalid-jid" };
["jid-required"] = { "modify", "bad-request", nil, "jid-required" };
@@ -33,16 +32,13 @@ local pubsub_errors = {
["precondition-not-met"] = { "cancel", "conflict", nil, "precondition-not-met" };
["invalid-item"] = { "modify", "bad-request", "invalid item" };
["persistent-items-unsupported"] = { "cancel", "feature-not-implemented", nil, "persistent-items" };
-};
-local function pubsub_error_reply(stanza, error)
- local e = pubsub_errors[error];
- if not e and errors.is_err(error) then
- e = { error.type, error.condition, error.text, error.pubsub_condition };
- end
- local reply = st.error_reply(stanza, t_unpack(e, 1, 3));
- if e[4] then
- reply:tag(e[4], { xmlns = xmlns_pubsub_errors }):up();
+});
+local function pubsub_error_reply(stanza, error, context)
+ local err = pubsub_errors.wrap(error, context);
+ if error == "precondition-not-met" and type(context) == "table" and type(context.field) == "string" then
+ err.text = "Field does not match: " .. context.field;
end
+ local reply = st.error_reply(stanza, err);
return reply;
end
_M.pubsub_error_reply = pubsub_error_reply;
@@ -206,23 +202,28 @@ local node_metadata_form = dataform {
};
{
type = "text-single";
- name = "pubsub#title";
+ name = "title";
+ var = "pubsub#title";
};
{
type = "text-single";
- name = "pubsub#description";
+ name = "description";
+ var = "pubsub#description";
};
{
type = "text-single";
- name = "pubsub#type";
+ name = "payload_type";
+ var = "pubsub#type";
};
{
type = "text-single";
- name = "pubsub#access_model";
+ name = "access_model";
+ var = "pubsub#access_model";
};
{
type = "text-single";
- name = "pubsub#publish_model";
+ name = "publish_model";
+ var = "pubsub#publish_model";
};
};
_M.node_metadata_form = node_metadata_form;
@@ -294,27 +295,14 @@ end
function _M.handle_disco_info_node(event, service)
local stanza, reply, node = event.stanza, event.reply, event.node;
- local ok, ret = service:get_nodes(stanza.attr.from);
+ local ok, meta = service:get_node_metadata(node, stanza.attr.from);
if not ok then
- event.origin.send(pubsub_error_reply(stanza, ret));
- return true;
- end
- local node_obj = ret[node];
- if not node_obj then
- event.origin.send(pubsub_error_reply(stanza, "item-not-found"));
+ event.origin.send(pubsub_error_reply(stanza, meta));
return true;
end
event.exists = true;
reply:tag("identity", { category = "pubsub", type = "leaf" }):up();
- if node_obj.config then
- reply:add_child(node_metadata_form:form({
- ["pubsub#title"] = node_obj.config.title;
- ["pubsub#description"] = node_obj.config.description;
- ["pubsub#type"] = node_obj.config.payload_type;
- ["pubsub#access_model"] = node_obj.config.access_model;
- ["pubsub#publish_model"] = node_obj.config.publish_model;
- }, "result"));
- end
+ reply:add_child(node_metadata_form:form(meta, "result"));
end
function _M.handle_disco_items_node(event, service)
@@ -685,7 +673,7 @@ function handlers.set_publish(origin, stanza, publish, service)
if item then
item.attr.publisher = service.config.normalize_jid(stanza.attr.from);
end
- local ok, ret = service:publish(node, stanza.attr.from, id, item, required_config);
+ local ok, ret, context = service:publish(node, stanza.attr.from, id, item, required_config);
local reply;
if ok then
if type(ok) == "string" then
@@ -696,7 +684,7 @@ function handlers.set_publish(origin, stanza, publish, service)
:tag("publish", { node = node })
:tag("item", { id = id });
else
- reply = pubsub_error_reply(stanza, ret);
+ reply = pubsub_error_reply(stanza, ret, context);
end
origin.send(reply);
return true;
diff --git a/plugins/mod_s2s.lua b/plugins/mod_s2s.lua
index 04fd5bc3..638ace3d 100644
--- a/plugins/mod_s2s.lua
+++ b/plugins/mod_s2s.lua
@@ -13,7 +13,6 @@ local hosts = prosody.hosts;
local core_process_stanza = prosody.core_process_stanza;
local tostring, type = tostring, type;
-local t_insert = table.insert;
local traceback = debug.traceback;
local add_task = require "prosody.util.timer".add_task;
@@ -33,6 +32,7 @@ local service = require "prosody.net.resolvers.service";
local resolver_chain = require "prosody.net.resolvers.chain";
local errors = require "prosody.util.error";
local set = require "prosody.util.set";
+local queue = require "prosody.util.queue";
local connect_timeout = module:get_option_period("s2s_timeout", 90);
local stream_close_timeout = module:get_option_period("s2s_close_timeout", 5);
@@ -42,6 +42,7 @@ local secure_domains, insecure_domains =
module:get_option_set("s2s_secure_domains", {})._items, module:get_option_set("s2s_insecure_domains", {})._items;
local require_encryption = module:get_option_boolean("s2s_require_encryption", true);
local stanza_size_limit = module:get_option_integer("s2s_stanza_size_limit", 1024*512, 10000);
+local sendq_size = module:get_option_integer("s2s_send_queue_size", 1024*32, 1);
local advertised_idle_timeout = 14*60; -- default in all net.server implementations
local network_settings = module:get_option("network_settings");
@@ -134,7 +135,7 @@ local bouncy_stanzas = { message = true, presence = true, iq = true };
local function bounce_sendq(session, reason)
local sendq = session.sendq;
if not sendq then return; end
- session.log("info", "Sending error replies for %d queued stanzas because of failed outgoing connection to %s", #sendq, session.to_host);
+ session.log("info", "Sending error replies for %d queued stanzas because of failed outgoing connection to %s", sendq.count(), session.to_host);
local dummy = {
type = "s2sin";
send = function ()
@@ -153,12 +154,12 @@ local function bounce_sendq(session, reason)
if session.had_stream then -- set when a stream is opened by the remote
error_type, condition = "wait", "remote-server-timeout";
end
- if errors.is_err(reason) then
+ if errors.is_error(reason) then
error_type, condition, reason_text = reason.type, reason.condition, reason.text;
elseif type(reason) == "string" then
reason_text = reason;
end
- for i, stanza in ipairs(sendq) do
+ for stanza in sendq:consume() do
if not stanza.attr.xmlns and bouncy_stanzas[stanza.name] and stanza.attr.type ~= "error" and stanza.attr.type ~= "result" then
local reply = st.error_reply(
stanza,
@@ -170,7 +171,6 @@ local function bounce_sendq(session, reason)
else
(session.log or log)("debug", "Not eligible for bouncing, discarding %s", stanza:top_tag());
end
- sendq[i] = nil;
end
session.sendq = nil;
end
@@ -194,11 +194,14 @@ function route_to_existing_session(event)
(host.log or log)("debug", "trying to send over unauthed s2sout to "..to_host);
-- Queue stanza until we are able to send it
- if host.sendq then
- t_insert(host.sendq, st.clone(stanza));
- else
+ if not host.sendq then
-- luacheck: ignore 122
- host.sendq = { st.clone(stanza) };
+ host.sendq = queue.new(sendq_size);
+ end
+ if not host.sendq:push(st.clone(stanza)) then
+ host.log("warn", "stanza [%s] not queued ", stanza.name);
+ event.origin.send(st.error_reply(stanza, "wait", "resource-constraint", "Outgoing stanza queue full"));
+ return true;
end
host.log("debug", "stanza [%s] queued ", stanza.name);
return true;
@@ -223,7 +226,8 @@ function route_to_new_session(event)
-- Store in buffer
host_session.bounce_sendq = bounce_sendq;
- host_session.sendq = { st.clone(stanza) };
+ host_session.sendq = queue.new(sendq_size);
+ host_session.sendq:push(st.clone(stanza));
log("debug", "stanza [%s] queued until connection complete", stanza.name);
-- FIXME Cleaner solution to passing extra data from resolvers to net.server
-- This mt-clone allows resolvers to add extra data, currently used for DANE TLSA records
@@ -362,11 +366,11 @@ function mark_connected(session)
if session.direction == "outgoing" then
if sendq then
- session.log("debug", "sending %d queued stanzas across new outgoing connection to %s", #sendq, session.to_host);
+ session.log("debug", "sending %d queued stanzas across new outgoing connection to %s", sendq.count(), session.to_host);
local send = session.sends2s;
- for i, stanza in ipairs(sendq) do
+ for stanza in sendq:consume() do
+ -- TODO check send success
send(stanza);
- sendq[i] = nil;
end
session.sendq = nil;
end
diff --git a/plugins/mod_smacks.lua b/plugins/mod_smacks.lua
index d4f0f371..7a9a67b3 100644
--- a/plugins/mod_smacks.lua
+++ b/plugins/mod_smacks.lua
@@ -541,11 +541,13 @@ module:hook("pre-resource-unbind", function (event)
return
end
- session.log("debug", "Destroying session for hibernating too long");
- save_old_session(session);
- session.resumption_token = nil;
- sessionmanager.destroy_session(session, "Hibernating too long");
- sessions_expired(1);
+ prosody.main_thread:run(function ()
+ session.log("debug", "Destroying session for hibernating too long");
+ save_old_session(session);
+ session.resumption_token = nil;
+ sessionmanager.destroy_session(session, "Hibernating too long");
+ sessions_expired(1);
+ end);
end);
if session.conn then
local conn = session.conn;
diff --git a/plugins/muc/hats.lib.lua b/plugins/muc/hats.lib.lua
index 492dc72c..7eb71eb4 100644
--- a/plugins/muc/hats.lib.lua
+++ b/plugins/muc/hats.lib.lua
@@ -25,7 +25,7 @@ module:hook("muc-build-occupant-presence", function (event)
hats_el:tag("hat", { uri = hat_id, title = hat_data.title }):up();
if hats_compat then
- if not hats_el then
+ if not legacy_hats_el then
legacy_hats_el = st.stanza("hats", { xmlns = xmlns_hats_legacy });
end
legacy_hats_el:tag("hat", { uri = hat_id, title = hat_data.title }):up();
diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua
index b8f276cf..359afc87 100644
--- a/plugins/muc/muc.lib.lua
+++ b/plugins/muc/muc.lib.lua
@@ -304,10 +304,10 @@ function room_mt:publicise_occupant_status(occupant, x, nick, actor, reason, pre
-- General populace
for occupant_nick, n_occupant in self:each_occupant() do
if occupant_nick ~= occupant.nick then
- local pr = get_p(n_occupant);
if broadcast_roles[occupant.role or "none"] or force_unavailable then
- self:route_to_occupant(n_occupant, pr);
+ self:route_to_occupant(n_occupant, get_p(n_occupant));
elseif prev_role and broadcast_roles[prev_role] then
+ local pr = get_p(n_occupant);
pr.attr.type = 'unavailable';
self:route_to_occupant(n_occupant, pr);
end
@@ -339,16 +339,14 @@ function room_mt:send_occupant_list(to, filter)
local broadcast_bare_jids = {}; -- Track which bare JIDs we have sent presence for
for occupant_jid, occupant in self:each_occupant() do
broadcast_bare_jids[occupant.bare_jid] = true;
- if filter == nil or filter(occupant_jid, occupant) then
+ if (filter == nil or filter(occupant_jid, occupant)) and (to_bare == occupant.bare_jid or broadcast_roles[occupant.role or "none"]) then
local x = st.stanza("x", {xmlns='http://jabber.org/protocol/muc#user'});
self:build_item_list(occupant, x, is_anonymous and to_bare ~= occupant.bare_jid); -- can always see your own jids
local pres = st.clone(occupant:get_presence());
pres.attr.to = to;
pres:add_child(x);
module:fire_event("muc-build-occupant-presence", { room = self, occupant = occupant, stanza = pres });
- if to_bare == occupant.bare_jid or broadcast_roles[occupant.role or "none"] then
- self:route_stanza(pres);
- end
+ self:route_stanza(pres);
end
end
if broadcast_roles.none then
diff --git a/spec/scansion/bookmarks2.scs b/spec/scansion/bookmarks2.scs
new file mode 100644
index 00000000..0243ca54
--- /dev/null
+++ b/spec/scansion/bookmarks2.scs
@@ -0,0 +1,181 @@
+# Pubsub: Bookmarks 2.0
+
+[Client] Juliet
+ jid: admin@localhost
+ password: password
+
+// admin@localhost is assumed to have node creation privileges
+
+---------
+
+Juliet connects
+
+-- Generated with https://gitlab.com/xmpp-rs/xmpp-parsers:
+-- cargo run --example=generate-caps https://code.matthewwild.co.uk/scansion/ <<< "<query xmlns='http://jabber.org/protocol/disco#info'><identity category='client' name='scansion' type='bot'/><feature var='http://jabber.org/protocol/disco#info'/><feature var='urn:xmpp:bookmarks:1+notify'/></query>"
+Juliet sends:
+ <presence id='presence0'>
+ <c xmlns='http://jabber.org/protocol/caps'
+ hash='sha-1'
+ node='https://code.matthewwild.co.uk/scansion/'
+ ver='CPuQARM1gCTq2f6/ZjHUzWL2QHg='/>
+ <c xmlns='urn:xmpp:caps'>
+ <hash xmlns='urn:xmpp:hashes:2' algo='sha-256'>OTy9GPCvBZRvqzOHmD/ThA1WbBH3tNoeKbdqKQCRPHc=</hash>
+ <hash xmlns='urn:xmpp:hashes:2' algo='sha3-256'>f/rxDeTf6HyjQ382V3GEG/UfAs5IeclC05jBSBnVQCI=</hash>
+ <hash xmlns='urn:xmpp:hashes:2' algo='blake2b-256'>ucfqg/NrLj0omE+26hYMrbpcmxHcU4Z3hfAQIF+6tt0=</hash>
+ </c>
+ </presence>
+
+Juliet receives:
+ <iq from="${Juliet's JID}" id='disco' type='get'>
+ <query xmlns='http://jabber.org/protocol/disco#info' node='https://code.matthewwild.co.uk/scansion/#CPuQARM1gCTq2f6/ZjHUzWL2QHg='/>
+ </iq>
+
+Juliet sends:
+ <iq to="${Juliet's JID}" id='disco' type='result'>
+ <query xmlns='http://jabber.org/protocol/disco#info' node='https://code.matthewwild.co.uk/scansion/#CPuQARM1gCTq2f6/ZjHUzWL2QHg='>
+ <identity category='client' name='scansion' type='bot'/>
+ <feature var='http://jabber.org/protocol/disco#info'/>
+ <feature var='urn:xmpp:bookmarks:1+notify'/>
+ </query>
+ </iq>
+
+Juliet sends:
+ <iq type='set' id='pub0'>
+ <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+ <publish node='urn:xmpp:bookmarks:1'>
+ <item id='theplay@conference.shakespeare.lit'>
+ <conference xmlns='urn:xmpp:bookmarks:1'
+ name='The Play&apos;s the Thing'
+ autojoin='true'>
+ <nick>JC</nick>
+ </conference>
+ </item>
+ </publish>
+ <publish-options>
+ <x xmlns='jabber:x:data' type='submit'>
+ <field var='FORM_TYPE' type='hidden'>
+ <value>http://jabber.org/protocol/pubsub#publish-options</value>
+ </field>
+ <field var='pubsub#persist_items'>
+ <value>true</value>
+ </field>
+ <field var='pubsub#max_items'>
+ <value>255</value>
+ </field>
+ <field var='pubsub#send_last_published_item'>
+ <value>never</value>
+ </field>
+ <field var='pubsub#access_model'>
+ <value>whitelist</value>
+ </field>
+ </x>
+ </publish-options>
+ </pubsub>
+ </iq>
+
+Juliet receives:
+ <message type='headline' from="${Juliet's JID}">
+ <event xmlns='http://jabber.org/protocol/pubsub#event'>
+ <items node='urn:xmpp:bookmarks:1'>
+ <item id='theplay@conference.shakespeare.lit'>
+ <conference xmlns='urn:xmpp:bookmarks:1'
+ name='The Play&apos;s the Thing'
+ autojoin='true'>
+ <nick>JC</nick>
+ </conference>
+ </item>
+ </items>
+ </event>
+ </message>
+
+Juliet receives:
+ <iq type='result' id='pub0'>
+ <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+ <publish node='urn:xmpp:bookmarks:1'>
+ <item id='theplay@conference.shakespeare.lit'/>
+ </publish>
+ </pubsub>
+ </iq>
+
+Juliet sends:
+ <iq type='set' id='pub1'>
+ <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+ <publish node='urn:xmpp:bookmarks:1'>
+ <item id='orchard@conference.shakespeare.lit'>
+ <conference xmlns='urn:xmpp:bookmarks:1'
+ name='The Orchard'
+ autojoin='true'>
+ <nick>JC</nick>
+ </conference>
+ </item>
+ </publish>
+ <publish-options>
+ <x xmlns='jabber:x:data' type='submit'>
+ <field var='FORM_TYPE' type='hidden'>
+ <value>http://jabber.org/protocol/pubsub#publish-options</value>
+ </field>
+ <field var='pubsub#persist_items'>
+ <value>true</value>
+ </field>
+ <field var='pubsub#max_items'>
+ <value>255</value>
+ </field>
+ <field var='pubsub#send_last_published_item'>
+ <value>never</value>
+ </field>
+ <field var='pubsub#access_model'>
+ <value>whitelist</value>
+ </field>
+ </x>
+ </publish-options>
+ </pubsub>
+ </iq>
+
+Juliet receives:
+ <message type='headline' from="${Juliet's JID}">
+ <event xmlns='http://jabber.org/protocol/pubsub#event'>
+ <items node='urn:xmpp:bookmarks:1'>
+ <item id='orchard@conference.shakespeare.lit'>
+ <conference xmlns='urn:xmpp:bookmarks:1'
+ name='The Orchard'
+ autojoin='true'>
+ <nick>JC</nick>
+ </conference>
+ </item>
+ </items>
+ </event>
+ </message>
+
+Juliet receives:
+ <iq type='result' id='pub1'>
+ <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+ <publish node='urn:xmpp:bookmarks:1'>
+ <item id='orchard@conference.shakespeare.lit'/>
+ </publish>
+ </pubsub>
+ </iq>
+
+Juliet sends:
+ <iq type='set' id='retract0'>
+ <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+ <retract node='urn:xmpp:bookmarks:1' notify='1'>
+ <item id='theplay@conference.shakespeare.lit'/>
+ </retract>
+ </pubsub>
+ </iq>
+
+Juliet receives:
+ <message type='headline' from="${Juliet's JID}">
+ <event xmlns='http://jabber.org/protocol/pubsub#event'>
+ <items node='urn:xmpp:bookmarks:1'>
+ <retract id='theplay@conference.shakespeare.lit'/>
+ </items>
+ </event>
+ </message>
+
+Juliet receives:
+ <iq type='result' id='retract0'/>
+
+Juliet disconnects
+
+// vim: syntax=xml:
diff --git a/spec/util_crypto_spec.lua b/spec/util_crypto_spec.lua
index 77d046ac..4a62e0bc 100644
--- a/spec/util_crypto_spec.lua
+++ b/spec/util_crypto_spec.lua
@@ -3,6 +3,7 @@ local test_keys = require "spec.inputs.test_keys";
describe("util.crypto", function ()
local crypto = require "util.crypto";
local random = require "util.random";
+ local encodings = require "util.encodings";
describe("generate_ed25519_keypair", function ()
local keypair = crypto.generate_ed25519_keypair();
@@ -10,6 +11,26 @@ describe("util.crypto", function ()
assert.equal("ED25519", keypair:get_type());
end)
+ describe("generate_p256_keypair", function ()
+ local keypair = crypto.generate_p256_keypair();
+ assert.is_not_nil(keypair);
+ assert.equal("id-ecPublicKey", keypair:get_type());
+ end)
+
+ describe("export/import raw", function ()
+ local keypair = crypto.generate_p256_keypair();
+ assert.is_not_nil(keypair);
+ local raw = keypair:public_raw()
+ local imported = crypto.import_public_ec_raw(raw, "P-256")
+ assert.equal(keypair:public_pem(), imported:public_pem());
+ end)
+
+ describe("derive", function ()
+ local key = crypto.import_private_pem(test_keys.ecdsa_private_pem);
+ local peer_key = crypto.import_public_pem(test_keys.ecdsa_public_pem);
+ assert.equal("n1v4KeKmOVwjC67fiKtjJnqcEaasbpZa2fLPNHW51co=", encodings.base64.encode(key:derive(peer_key)))
+ end)
+
describe("import_private_pem", function ()
it("can import ECDSA keys", function ()
local ecdsa_key = crypto.import_private_pem(test_keys.ecdsa_private_pem);
diff --git a/spec/util_error_spec.lua b/spec/util_error_spec.lua
index ebe8bff7..cd30ee94 100644
--- a/spec/util_error_spec.lua
+++ b/spec/util_error_spec.lua
@@ -29,10 +29,10 @@ describe("util.error", function ()
end);
- describe("is_err()", function ()
+ describe("is_error()", function ()
it("works", function ()
- assert.truthy(errors.is_err(errors.new()));
- assert.falsy(errors.is_err("not an error"));
+ assert.truthy(errors.is_error(errors.new()));
+ assert.falsy(errors.is_error("not an error"));
end);
end);
@@ -40,7 +40,7 @@ describe("util.error", function ()
it("works", function ()
local ok, err = errors.coerce(nil, "it dun goofed");
assert.is_nil(ok);
- assert.truthy(errors.is_err(err))
+ assert.truthy(errors.is_error(err))
end);
end);
@@ -50,7 +50,7 @@ describe("util.error", function ()
local m = st.message({ type = "chat" });
local e = st.error_reply(m, "modify", "bad-request", nil, "error.example"):tag("extra", { xmlns = "xmpp:example.test" });
local err = errors.from_stanza(e);
- assert.truthy(errors.is_err(err));
+ assert.truthy(errors.is_error(err));
assert.equal("modify", err.type);
assert.equal("bad-request", err.condition);
assert.equal(e, err.context.stanza);
@@ -187,7 +187,7 @@ describe("util.error", function ()
end
local ok, err = reg.coerce(test());
assert.is_nil(ok);
- assert.is_truthy(errors.is_err(err));
+ assert.is_truthy(errors.is_error(err));
assert.equal("forbidden", err.condition);
end);
@@ -208,7 +208,7 @@ describe("util.error", function ()
end
local ok, err = reg.coerce(test());
assert.is_nil(ok);
- assert.is_truthy(errors.is_err(err));
+ assert.is_truthy(errors.is_error(err));
assert.equal("internal-server-error", err.condition);
assert.equal("Oh no", err.text);
end);
diff --git a/spec/util_pubsub_spec.lua b/spec/util_pubsub_spec.lua
index 45a612a0..a03ffa64 100644
--- a/spec/util_pubsub_spec.lua
+++ b/spec/util_pubsub_spec.lua
@@ -108,7 +108,7 @@ describe("util.pubsub", function ()
it("fails to publish to a node with differing config", function ()
local ok, err = service:publish("node", true, "1", "item 2", { myoption = false });
assert.falsy(ok);
- assert.equals("precondition-not-met", err.pubsub_condition);
+ assert.equals("precondition-not-met", err);
end);
it("allows to publish to a node with differing config when only defaults are suggested", function ()
@@ -605,4 +605,14 @@ describe("util.pubsub", function ()
end);
end)
+
+ describe("metadata", function()
+ it("works", function()
+ local service = pubsub.new { metadata_subset = { "title" } };
+ assert.truthy(service:create("node", true, { title = "Hello", secret = "hidden" }))
+ local ok, meta = service:get_node_metadata("node", "nobody");
+ assert.truthy(ok, meta);
+ assert.same({ title = "Hello" }, meta);
+ end)
+ end);
end);
diff --git a/spec/util_queue_spec.lua b/spec/util_queue_spec.lua
index d73f523d..d9e92e3d 100644
--- a/spec/util_queue_spec.lua
+++ b/spec/util_queue_spec.lua
@@ -137,4 +137,23 @@ describe("util.queue", function()
assert.equal(c, 6);
end);
end);
+ describe("replace()", function ()
+ it("should work", function ()
+ local q = queue.new(10);
+ for i = 1, 5 do
+ q:push(i);
+ end
+ q:replace(6);
+ local c = 0;
+ for i in q:consume() do
+ c = c + 1;
+ if c > 1 then
+ assert.is_equal(c, i);
+ elseif c == 1 then
+ assert.is_equal(6, i);
+ end
+ end
+ assert.is_equal(5, c);
+ end);
+ end);
end);
diff --git a/teal-src/prosody/util/xtemplate.tl b/teal-src/prosody/util/xtemplate.tl
index 84003051..4d293359 100644
--- a/teal-src/prosody/util/xtemplate.tl
+++ b/teal-src/prosody/util/xtemplate.tl
@@ -17,7 +17,7 @@ local t_concat = table.concat;
local st = require "prosody.util.stanza";
local type escape_t = function (string) : string
-local type filter_t = function (string, string | st.stanza_t, string) : string | st.stanza_t, boolean
+local type filter_t = function (string | st.stanza_t, string | st.stanza_t, string) : string | st.stanza_t, boolean
local type filter_coll = { string : filter_t }
local function render(template : string, root : st.stanza_t, escape : escape_t, filters : filter_coll) : string
@@ -85,11 +85,7 @@ local function render(template : string, root : st.stanza_t, escape : escape_t,
end
elseif filters and filters[func] then
local f = filters[func];
- if args == nil then
- value, is_escaped = f(value, tmpl);
- else
- value, is_escaped = f(args, value, tmpl);
- end
+ value, is_escaped = f(value, args, tmpl);
else
error("No such filter function: " .. func);
end
diff --git a/util-src/crypto.c b/util-src/crypto.c
index 1e69599d..68733c7c 100644
--- a/util-src/crypto.c
+++ b/util-src/crypto.c
@@ -27,6 +27,7 @@ typedef unsigned __int32 uint32_t;
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/obj_mac.h>
+#include <openssl/param_build.h>
#include <openssl/pem.h>
#if (LUA_VERSION_NUM == 501)
@@ -92,6 +93,40 @@ static int Lpkey_meth_get_type(lua_State *L) {
return 1;
}
+static int Lpkey_meth_derive(lua_State *L) {
+ size_t outlen;
+ EVP_PKEY *key = pkey_from_arg(L, 1, 0, 0);
+ EVP_PKEY *peer = pkey_from_arg(L, 2, 0, 0);
+ EVP_PKEY_CTX *ctx;
+ BUF_MEM *buf;
+ BIO *bio = new_managed_BIO_s_mem(L);
+ BIO_get_mem_ptr(bio, &buf);
+ if (!(ctx = EVP_PKEY_CTX_new(key, NULL)))
+ goto sslerr;
+ if (EVP_PKEY_derive_init(ctx) <= 0)
+ goto sslerr;
+ if (EVP_PKEY_derive_set_peer(ctx, peer) <= 0)
+ goto sslerr;
+ if (EVP_PKEY_derive(ctx, NULL, &outlen) <= 0)
+ goto sslerr;
+ if (!BUF_MEM_grow_clean(buf, outlen))
+ goto sslerr;
+ if (EVP_PKEY_derive(ctx, (unsigned char*)buf->data, &outlen) <= 0)
+ goto sslerr;
+ EVP_PKEY_CTX_free(ctx);
+ ctx = NULL;
+ lua_pushlstring(L, buf->data, outlen);
+ BIO_reset(bio);
+ return 1;
+sslerr:
+ if (ctx) {
+ EVP_PKEY_CTX_free(ctx);
+ ctx = NULL;
+ }
+ BIO_reset(bio);
+ return luaL_error(L, "pkey:derive failed");
+}
+
static int base_evp_sign(lua_State *L, const int key_type, const EVP_MD *digest_type) {
EVP_PKEY *pkey = pkey_from_arg(L, 1, (key_type!=NID_rsassaPss)?key_type:NID_rsaEncryption, 1);
luaL_Buffer sigbuf;
@@ -163,6 +198,28 @@ cleanup:
return 1;
}
+static int Lpkey_meth_public_raw(lua_State *L) {
+ OSSL_PARAM *params;
+ EVP_PKEY *pkey = pkey_from_arg(L, 1, 0, 0);
+
+ if (EVP_PKEY_todata(pkey, EVP_PKEY_PUBLIC_KEY, &params)) {
+ OSSL_PARAM *item = params;
+ while (item->key) {
+ if (!strcmp("pub", item->key)) {
+ lua_pushlstring(L, item->data, item->data_size);
+ break;
+ }
+ item++;
+ }
+ if (!item->key) lua_pushnil(L);
+ OSSL_PARAM_free(params);
+ } else {
+ lua_pushnil(L);
+ }
+
+ return 1;
+}
+
static int Lpkey_meth_public_pem(lua_State *L) {
char *data;
size_t bytes;
@@ -237,6 +294,25 @@ static int Lgenerate_ed25519_keypair(lua_State *L) {
return 1;
}
+static int Lgenerate_p256_keypair(lua_State *L) {
+ EVP_PKEY *pkey = NULL;
+ EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL);
+
+ /* Generate key */
+ if (EVP_PKEY_keygen_init(pctx) <= 0) goto err;
+ if (EVP_PKEY_CTX_set_ec_paramgen_curve_nid(pctx, NID_X9_62_prime256v1) <= 0) goto err;
+ if (EVP_PKEY_keygen(pctx, &pkey) <= 0) goto err;
+ EVP_PKEY_CTX_free(pctx);
+
+ push_pkey(L, pkey, NID_X9_62_prime256v1, 1);
+ return 1;
+
+err:
+ if (pctx) EVP_PKEY_CTX_free(pctx);
+ lua_pushnil(L);
+ return 1;
+}
+
static int Limport_private_pem(lua_State *L) {
EVP_PKEY *pkey = NULL;
@@ -257,6 +333,42 @@ static int Limport_private_pem(lua_State *L) {
return 1;
}
+static int Limport_public_ec_raw(lua_State *L) {
+ OSSL_PARAM_BLD *param_bld = NULL;
+ OSSL_PARAM *params = NULL;
+ EVP_PKEY_CTX *ctx = NULL;
+ EVP_PKEY *pkey = NULL;
+
+ size_t pubkey_bytes;
+ const char* pubkey_data = luaL_checklstring(L, 1, &pubkey_bytes);
+ const char* curve = luaL_checkstring(L, 2);
+
+ param_bld = OSSL_PARAM_BLD_new();
+ if (!param_bld) goto err;
+ if (!OSSL_PARAM_BLD_push_utf8_string(param_bld, "group", curve, 0)) goto err;
+ if (!OSSL_PARAM_BLD_push_octet_string(param_bld, "pub", pubkey_data, pubkey_bytes)) goto err;
+ params = OSSL_PARAM_BLD_to_param(param_bld);
+ if (!params) goto err;
+ ctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL);
+ if (!ctx) goto err;
+ if (!EVP_PKEY_fromdata_init(ctx)) goto err;
+ if (EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_PUBLIC_KEY, params) <= 0) goto err;
+
+ push_pkey(L, pkey, EVP_PKEY_id(pkey), 0);
+
+ EVP_PKEY_CTX_free(ctx);
+ OSSL_PARAM_free(params);
+ OSSL_PARAM_BLD_free(param_bld);
+
+ return 1;
+err:
+ if (ctx) EVP_PKEY_CTX_free(ctx);
+ if (params) OSSL_PARAM_free(params);
+ if (param_bld) OSSL_PARAM_BLD_free(param_bld);
+ lua_pushnil(L);
+ return 1;
+}
+
static int Limport_public_pem(lua_State *L) {
EVP_PKEY *pkey = NULL;
@@ -571,9 +683,11 @@ static const luaL_Reg Reg[] = {
{ "aes_256_ctr_decrypt", Laes_256_ctr_decrypt },
{ "generate_ed25519_keypair", Lgenerate_ed25519_keypair },
+ { "generate_p256_keypair", Lgenerate_p256_keypair },
{ "import_private_pem", Limport_private_pem },
{ "import_public_pem", Limport_public_pem },
+ { "import_public_ec_raw", Limport_public_ec_raw },
{ "parse_ecdsa_signature", Lparse_ecdsa_signature },
{ "build_ecdsa_signature", Lbuild_ecdsa_signature },
@@ -583,7 +697,9 @@ static const luaL_Reg Reg[] = {
static const luaL_Reg KeyMethods[] = {
{ "private_pem", Lpkey_meth_private_pem },
{ "public_pem", Lpkey_meth_public_pem },
+ { "public_raw", Lpkey_meth_public_raw },
{ "get_type", Lpkey_meth_get_type },
+ { "derive", Lpkey_meth_derive },
{ NULL, NULL }
};
diff --git a/util/dnsregistry.lua b/util/dnsregistry.lua
index c52abee9..b65debe0 100644
--- a/util/dnsregistry.lua
+++ b/util/dnsregistry.lua
@@ -1,5 +1,5 @@
-- Source: https://www.iana.org/assignments/dns-parameters/dns-parameters.xml
--- Generated on 2023-01-20
+-- Generated on 2024-10-26
return {
classes = {
["IN"] = 1; [1] = "IN";
@@ -79,6 +79,7 @@ return {
["LP"] = 107; [107] = "LP";
["EUI48"] = 108; [108] = "EUI48";
["EUI64"] = 109; [109] = "EUI64";
+ ["NXNAME"] = 128; [128] = "NXNAME";
["TKEY"] = 249; [249] = "TKEY";
["TSIG"] = 250; [250] = "TSIG";
["IXFR"] = 251; [251] = "IXFR";
@@ -91,6 +92,10 @@ return {
["AVC"] = 258; [258] = "AVC";
["DOA"] = 259; [259] = "DOA";
["AMTRELAY"] = 260; [260] = "AMTRELAY";
+ ["RESINFO"] = 261; [261] = "RESINFO";
+ ["WALLET"] = 262; [262] = "WALLET";
+ ["CLA"] = 263; [263] = "CLA";
+ ["IPN"] = 264; [264] = "IPN";
["TA"] = 32768; [32768] = "TA";
["DLV"] = 32769; [32769] = "DLV";
};
diff --git a/util/pubsub.lua b/util/pubsub.lua
index ccde8b53..d6779736 100644
--- a/util/pubsub.lua
+++ b/util/pubsub.lua
@@ -1,6 +1,5 @@
local events = require "prosody.util.events";
local cache = require "prosody.util.cache";
-local errors = require "prosody.util.error";
local service_mt = {};
@@ -12,6 +11,7 @@ local default_config = {
itemcheck = function () return true; end;
get_affiliation = function () end;
normalize_jid = function (jid) return jid; end;
+ metadata_subset = {};
capabilities = {
outcast = {
create = false;
@@ -46,6 +46,7 @@ local default_config = {
get_subscription = true;
get_subscriptions = true;
get_items = false;
+ get_metadata = true;
subscribe_other = false;
unsubscribe_other = false;
@@ -68,6 +69,7 @@ local default_config = {
get_subscription = true;
get_subscriptions = true;
get_items = true;
+ get_metadata = true;
subscribe_other = false;
unsubscribe_other = false;
@@ -91,6 +93,7 @@ local default_config = {
get_subscription = true;
get_subscriptions = true;
get_items = true;
+ get_metadata = true;
subscribe_other = false;
unsubscribe_other = false;
@@ -116,6 +119,7 @@ local default_config = {
get_subscription = true;
get_subscriptions = true;
get_items = true;
+ get_metadata = true;
subscribe_other = true;
@@ -562,11 +566,7 @@ function service:publish(node, actor, id, item, requested_config) --> ok, err
-- Check that node has the requested config before we publish
local ok, field = check_preconditions(node_obj.config, requested_config);
if not ok then
- local err = errors.new({
- type = "cancel", condition = "conflict", text = "Field does not match: "..field;
- });
- err.pubsub_condition = "precondition-not-met";
- return false, err;
+ return false, "precondition-not-met", { field = field };
end
end
if not self.config.itemcheck(item) then
@@ -877,6 +877,20 @@ function service:get_node_config(node, actor) --> (true, config) or (false, err)
return true, config_table;
end
+function service:get_node_metadata(node, actor)
+ if not self:may(node, actor, "get_metadata") then
+ return false, "forbidden";
+ end
+
+ local ok, config = self:get_node_config(node, true);
+ if not ok then return ok, config; end
+ local meta = {};
+ for _, k in ipairs(self.config.metadata_subset) do
+ meta[k] = config[k];
+ end
+ return true, meta;
+end
+
return {
new = new;
};
diff --git a/util/xtemplate.lua b/util/xtemplate.lua
index e23b1a01..446d7d1f 100644
--- a/util/xtemplate.lua
+++ b/util/xtemplate.lua
@@ -70,11 +70,7 @@ local function render(template, root, escape, filters)
end
elseif filters and filters[func] then
local f = filters[func];
- if args == nil then
- value, is_escaped = f(value, tmpl);
- else
- value, is_escaped = f(args, value, tmpl);
- end
+ value, is_escaped = f(value, args, tmpl);
else
error("No such filter function: " .. func);
end