Software /
code /
prosody-modules
Changeset
5118:7bce75e74f86
Merge
author | Kim Alvefur <zash@zash.se> |
---|---|
date | Sun, 18 Dec 2022 15:30:02 +0100 |
parents | 5114:d2a84e6aed2b (diff) 5117:2b94ee74d1f1 (current diff) |
children | 5119:048e339706ba |
files | mod_http_muc_log/mod_http_muc_log.lua mod_http_muc_log/static/style.css mod_http_muc_log/static/timestamps.js |
diffstat | 26 files changed, 421 insertions(+), 146 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_clean_roster/README.md Sun Dec 18 15:30:02 2022 +0100 @@ -0,0 +1,5 @@ +Removes invalid characters from roster entries. + +```bash +sudo prosodyctl mod_clean_roster +```
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_clean_roster/mod_clean_roster.lua Sun Dec 18 15:30:02 2022 +0100 @@ -0,0 +1,60 @@ +local s_find = string.find; + +local pctl = require "util.prosodyctl"; + +local rostermanager = require "core.rostermanager"; +local storagemanager = require "core.storagemanager"; +local usermanager = require "core.usermanager"; + +-- copypaste from util.stanza +local function valid_xml_cdata(str, attr) + return not s_find(str, attr and "[^\1\9\10\13\20-~\128-\247]" or "[^\9\10\13\20-~\128-\247]"); +end + +function module.command(_arg) + if select(2, pctl.isrunning()) then + pctl.show_warning("Stop Prosody before running this command"); + return 1; + end + + for hostname, host in pairs(prosody.hosts) do + if hostname ~= "*" then + if host.users.name == "null" then + storagemanager.initialize_host(hostname); + usermanager.initialize_host(hostname); + end + local fixes = 0; + for username in host.users.users() do + local roster = rostermanager.load_roster(username, hostname); + local changed = false; + for contact, item in pairs(roster) do + if contact ~= false then + if item.name and not valid_xml_cdata(item.name, false) then + item.name = item.name:gsub("[^\9\10\13\20-~\128-\247]", "�"); + fixes = fixes + 1; + changed = true; + end + local clean_groups = {}; + for group in pairs(item.groups) do + if valid_xml_cdata(group, false) then + clean_groups[group] = true; + else + clean_groups[group:gsub("[^\9\10\13\20-~\128-\247]", "�")] = true; + fixes = fixes + 1; + changed = true; + end + end + item.groups = clean_groups; + else + -- pending entries etc + end + end + if changed then + assert(rostermanager.save_roster(username, hostname, roster)); + end + end + pctl.show_message("Fixed %d items on host %s", fixes, hostname); + end + end + return 0; +end
--- a/mod_compat_roles/mod_compat_roles.lua Sat Dec 17 14:13:06 2022 +0100 +++ b/mod_compat_roles/mod_compat_roles.lua Sun Dec 18 15:30:02 2022 +0100 @@ -28,15 +28,26 @@ return get_jid_role_name(username.."@"..host, host); end --- permissions[host][permission_name] = permitted_role_name +-- permissions[host][role_name][permission_name] = is_permitted local permissions = {}; -local function role_may(role_name, permission) - local role_permissions = permissions[role_name]; +local role_inheritance = { + ["prosody:operator"] = "prosody:admin"; + ["prosody:admin"] = "prosody:user"; + ["prosody:user"] = "prosody:restricted"; +}; + +local function role_may(host, role_name, permission) + local host_roles = permissions[host]; + if not host_roles then + return false; + end + local role_permissions = host_roles[role_name]; if not role_permissions then return false; end - return not not permissions[role_name][permission]; + local next_role = role_inheritance[role_name]; + return not not permissions[role_name][permission] or (next_role and role_may(host, next_role, permission)); end function moduleapi.may(self, action, context) @@ -56,7 +67,7 @@ return false; end - local permit = role_may(role, action); + local permit = role_may(self.host, role, action); if not permit then self:log("debug", "Access denied: JID <%s> may not %s (not permitted by role %s)", context, action, role.name); end @@ -74,7 +85,7 @@ self:log("debug", "Access denied: JID <%s> may not %s (no role found)", actor_jid, action); return false; end - local permit = role_may(role_name, action, context); + local permit = role_may(self.host, role_name, action, context); if not permit then self:log("debug", "Access denied: JID <%s> may not %s (not permitted by role %s)", actor_jid, action, role_name); end @@ -83,10 +94,15 @@ end function moduleapi.default_permission(self, role_name, permission) - local r = permissions[self.host][role_name]; + local p = permissions[self.host]; + if not p then + p = {}; + permissions[self.host] = p; + end + local r = p[role_name]; if not r then r = {}; - permissions[self.host][role_name] = r; + p[role_name] = r; end r[permission] = true; end
--- a/mod_http_muc_log/README.markdown Sat Dec 17 14:13:06 2022 +0100 +++ b/mod_http_muc_log/README.markdown Sun Dec 18 15:30:02 2022 +0100 @@ -6,6 +6,7 @@ build: copy_directories: - res + - static ... Introduction
--- a/mod_http_muc_log/mod_http_muc_log.lua Sat Dec 17 14:13:06 2022 +0100 +++ b/mod_http_muc_log/mod_http_muc_log.lua Sun Dec 18 15:30:02 2022 +0100 @@ -40,6 +40,8 @@ end end +local resources = module:get_option_path(module.name .. "_resources", "static"); + -- local base_url = module:http_url() .. '/'; -- TODO: Generate links in a smart way local get_link do local link, path = { path = '/' }, { "", "", is_directory = true }; @@ -248,6 +250,7 @@ response.headers.content_type = "text/html; charset=utf-8"; local room_obj = get_room(room); return render(template, { + static = "../@static"; room = room_obj._data; jid = room_obj.jid; jid_node = jid_split(room_obj.jid); @@ -467,6 +470,7 @@ response.headers.content_type = "text/html; charset=utf-8"; local room_obj = get_room(room); return render(template, { + static = "../@static"; date = date; room = room_obj._data; jid = room_obj.jid; @@ -517,6 +521,7 @@ response.headers.content_type = "text/html; charset=utf-8"; return render(template, { + static = "./@static"; title = module:get_option_string("name", "Prosody Chatrooms"); jid = module.host; hide_presence = hide_presence(request); @@ -526,6 +531,20 @@ }); end +local serve_static +do + if prosody.process_type == "prosody" then + -- Prosody >= 0.12 + local http_files = require "net.http.files"; + serve = http_files.serve; + else + -- Prosody <= 0.11 + serve = module:depends "http_files".serve; + end + local mime_map = module:shared("/*/http_files/mime").types or { css = "text/css"; js = "application/javascript" }; + serve_static = serve({ path = resources; mime_map = mime_map }); +end + module:provides("http", { title = module:get_option_string("name", "Chatroom logs"); route = { @@ -535,6 +554,10 @@ -- thus: -- GET /room --> years_page (via logs_page) -- GET /room/yyyy-mm-dd --> logs_page (for real) + + ["GET /@static/*"] = serve_static; + -- There are not many ASCII characters that are safe to use in URLs but not + -- valid in JID localparts, '@' seemed the only option. }; });
--- a/mod_http_muc_log/res/http_muc_log.html Sat Dec 17 14:13:06 2022 +0100 +++ b/mod_http_muc_log/res/http_muc_log.html Sun Dec 18 15:30:02 2022 +0100 @@ -5,59 +5,7 @@ <meta name="viewport" content="width=device-width, initial-scale=1"> {date&<meta name="dcterms.date" content="{date}">} <title>{title?{room.name?{jid_node}}{date& - {date}}}</title> -<style> -:link,:visited{color:#3465a4;text-decoration:none;} -:link:hover,:visited:hover{color:#6197df;} -body{background-color:#eeeeec;margin:1ex 0;padding-bottom:3em;font-family:Arial,Helvetica,sans-serif;} -ul,ol{padding:0;} -li{list-style:none;} -hr{visibility:hidden;clear:both;} -br{clear:both;} -header,footer{margin:1ex 1em;} -footer{font-size:smaller;color:#babdb6;} -nav{font-size:large;margin:1ex 1ex;clear:both;line-height:1.5em;} -footer nav .up{display:none;} -@media screen and (min-width: 460px) { -nav {font-size:x-large;margin:1ex 1em;} -} -nav a{padding:1ex} -nav li,nav dt{margin:1ex} -nav .up{font-size:smaller;display:block;clear:both;} -nav .up::before{content:"↑ ";} -nav .prev{float:left;} -nav .next{float:right;} -nav .next::after{content:" →";} -nav .prev::before{content:"← ";} -nav .last::after{content:" ⇥";} -nav :empty::after,nav :empty::before{content:""} -table{display:inline-block; margin:1ex 1em;vertical-align:top;} -th{font-size:x-small} -td{text-align:right;color:#bababa} -td > a, td > span{padding:0.4em} -.content{background-color:white;padding:1em;list-style-position:inside;} -.time{float:right;font-size:small;opacity:0.2;} -li:hover .time{opacity:1;} -.description{font-size:smaller;} -.body{white-space:pre-line;} -.body::before,.body::after{content:"";} -.presence .verb{font-style:normal;color:#30c030;} -.unavailable .verb{color:#c03030;} -.button{display:inline-block} -.button>a{color:white;background-color:orange;border-radius:4px} -.reaction{font-size:smaller;outline:1px solid silver;border-radius:2px} -form{text-align:right} -li.edited{display:none} -li:target{outline:1px gray dotted;display:inherit} -figure img{max-height:9em;max-width:16em} -@media (prefers-color-scheme: dark) { -html{color:#eee} -body{background-color:#161616} -.content{background-color:#1c1c1c} -footer{color:#444} -td{color:#444} -.button>a{background-color:#282828} -} -</style> +<link rel="stylesheet" type="text/css" href="{static}/style.css"> </head> <body> <header> @@ -110,11 +58,11 @@ </div> <ol class="chat-logs">{lines# -<li {item.lang&lang="{item.lang}"} class="{item.st_name} {item.st_type?} {item.edited&edited}" id="{item.archive_id}"> -<a class="time" href="#{item.archive_id}"><time id="{item.time}" datetime="{item.datetime}">{item.time}</time></a> +<li class="{item.st_name} {item.st_type?} {item.edited&edited}" id="{item.archive_id}"> <b class="nick">{item.nick}</b> <em class="verb">{item.verb?}</em> -<q class="body">{item.edited&<del>}{item.body?}{item.edited&</del> <a href="#{item.edited}" title="jump to corrected version">✎</a>}{item.edit& <a href="#{item.edit}" title="jump to previous version">✏</a>}{item.reply& <a href="#{item.reply}" title="jump to message responded to">↺</a>}</q> +<a class="time" href="#{item.archive_id}"><time id="{item.time}" datetime="{item.datetime}">{item.time}</time></a> +<p {item.lang&lang="{item.lang}"} class="body">{item.edited&<del>}{item.body?}{item.edited&</del> <a href="#{item.edited}" title="jump to corrected version">✎</a>}{item.edit& <a href="#{item.edit}" title="jump to previous version">✏</a>}{item.reply& <a href="#{item.reply}" title="jump to message responded to">↺</a>}</p> {item.reactions%<span class="reaction">{idx} {item}</span>} {item.oob.url&<figure><a rel="nofollow" href="{item.oob.url?}"><img alt="{item.oob.desc?}" src="{item.oob.url?}"/></a><figcaption>{item.oob.desc?}</figcaption></figure>} </li>} @@ -130,25 +78,6 @@ <br> <div class="powered-by">Prosody</div> </footer> -<script> -/* -* Local timestamps -*/ -(function () { -var timeTags = document.getElementsByTagName("time"); -var i = 0, tag, date; -while(timeTags[i]) { -tag = timeTags[i++]; -if(date = tag.getAttribute("datetime")) { -date = new Date(date); -tag.textContent = date.toLocaleTimeString(navigator.language); -tag.setAttribute("title", date.toString()); -} -} -document.forms[0].elements.p.addEventListener("change", function() { -document.forms[0].submit(); -}); -})(); -</script> +<script defer type="application/javascript" src="{static}/timestamps.js"></script> </body> </html>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_http_muc_log/static/style.css Sun Dec 18 15:30:02 2022 +0100 @@ -0,0 +1,81 @@ +@charset "UTF-8"; +/* Style for mod_http_muc_log */ +:link, :visited { color: #3465a4; text-decoration: none; } + +:link:hover, :link:hover, :visited:hover, :visited:hover { color: #6197df; } + +body { background-color: #eeeeec; margin: 1ex 0; padding-bottom: 3em; font-family: Arial,Helvetica,sans-serif; } + +ul, ol { padding: 0; } + +li { list-style: none; } + +li.edited { display: none; } + +li:target { outline: 1px gray dotted; display: inherit; } + +hr { visibility: hidden; clear: both; } + +br { clear: both; } + +header, footer { margin: 1ex 1em; } + +footer { font-size: smaller; color: #babdb6; } + +footer nav .up { display: none; } + +nav { font-size: large; margin: 1ex 1ex; clear: both; line-height: 1.5em; } + +nav a { padding: 1ex; } + +nav li, nav dt { margin: 1ex; } + +nav .up { font-size: smaller; display: block; clear: both; } + +nav .up::before { content: "↑ "; } + +nav .prev { float: left; } + +nav .prev::before { content: "← "; } + +nav .next { float: right; } + +nav .next::after { content: " →"; } + +nav .last::after { content: " ⇥"; } + +nav :empty::after, nav :empty::before { content: ""; } + +@media screen and (min-width: 460px) { nav { font-size: x-large; margin: 1ex 1em; } } + +table { display: inline-block; margin: 1ex 1em; vertical-align: top; } + +th { font-size: x-small; } + +td { text-align: right; color: #bababa; } + +td > a, td > span { padding: 0.4em; } + +.content { background-color: white; padding: 1em; list-style-position: inside; } + +.time { margin-left: 1em; font-size: small; } + +.description { font-size: smaller; } + +.body { white-space: pre-line; margin: 1pt 0 1ex; } + +.presence .verb { font-style: normal; color: #30c030; } + +.unavailable .verb { color: #c03030; } + +.button { display: inline-block; } + +.button > a { color: white; background-color: orange; border-radius: 4px; } + +.reaction { font-size: smaller; outline: 1px solid silver; border-radius: 2px; } + +form { text-align: right; } + +figure img { max-height: 9em; max-width: 16em; } + +@media (prefers-color-scheme: dark) { html { color: #eee; } body { background-color: #161616; } .content { background-color: #1c1c1c; } footer { color: #444; } td { color: #444; } .button > a { background-color: #282828; } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_http_muc_log/static/timestamps.js Sun Dec 18 15:30:02 2022 +0100 @@ -0,0 +1,21 @@ +/* +* Local timestamps +*/ +(function () { +var timeTags = document.getElementsByTagName("time"); +var i = 0, tag, date; +while(timeTags[i]) { +tag = timeTags[i++]; +if(date = tag.getAttribute("datetime")) { +date = new Date(date); +tag.textContent = date.toLocaleTimeString(navigator.language); +tag.setAttribute("title", date.toString()); +} +} +if(document.forms.length>0){ +document.forms[0].elements.p.addEventListener("change", function() { +document.forms[0].submit(); +}); +} +})(); +
--- a/mod_isolate_host/mod_isolate_host.lua Sat Dec 17 14:13:06 2022 +0100 +++ b/mod_isolate_host/mod_isolate_host.lua Sun Dec 18 15:30:02 2022 +0100 @@ -39,7 +39,7 @@ function check_user_isolated(event) local session = event.session; local bare_jid = jid_bare(session.full_jid); - if module:may("xmpp:federate") or except_users:contains(bare_jid) then + if module:may("xmpp:federate", event) or except_users:contains(bare_jid) then session.no_host_isolation = true; end module:log("debug", "%s is %sisolated", session.full_jid or "[?]", session.no_host_isolation and "" or "not ");
--- a/mod_password_reset/README.markdown Sat Dec 17 14:13:06 2022 +0100 +++ b/mod_password_reset/README.markdown Sun Dec 18 15:30:02 2022 +0100 @@ -2,6 +2,10 @@ labels: - 'Stage-Alpha' summary: 'Enables users to reset their password via a link' +rockspec: + build: + copy_directories: + - password_reset ... Introduction
--- a/mod_pubsub_feeds/README.markdown Sat Dec 17 14:13:06 2022 +0100 +++ b/mod_pubsub_feeds/README.markdown Sun Dec 18 15:30:02 2022 +0100 @@ -1,5 +1,9 @@ --- summary: Subscribe to Atom and RSS feeds over pubsub +rockspec: + build: + modules: + pubsub_feeds.feeds: feeds.lib.lua --- # Introduction
--- a/mod_pubsub_mqtt/mod_pubsub_mqtt.lua Sat Dec 17 14:13:06 2022 +0100 +++ b/mod_pubsub_mqtt/mod_pubsub_mqtt.lua Sun Dec 18 15:30:02 2022 +0100 @@ -1,14 +1,52 @@ module:set_global(); local mqtt = module:require "mqtt"; +local id = require "util.id"; local st = require "util.stanza"; +local function tostring_content(item) + return tostring(item[1]); +end + +local data_translators = setmetatable({ + utf8 = { + from_item = function (item) + return item:find("{https://prosody.im/protocol/data}data#"); + end; + to_item = function (payload) + return st.stanza("item", { xmlns = "http://jabber.org/protocol/pubsub", id = id.medium() }) + :text_tag("data", payload, { xmlns = "https://prosody.im/protocol/data" }) + end; + }; + json = { + from_item = function (item) + return item:find("{urn:xmpp:json:0}json#"); + end; + to_item = function (payload) + return st.stanza("item", { xmlns = "http://jabber.org/protocol/pubsub", id = id.medium() }) + :text_tag("json", payload, { xmlns = "urn:xmpp:json:0" }); + end; + }; + atom_title = { + from_item = function (item) + return item:find("{http://www.w3.org/2005/Atom}entry/title#"); + end; + to_item = function (payload) + return st.stanza("item", { xmlns = "http://jabber.org/protocol/pubsub", id = id.medium() }) + :tag("entry", { xmlns = "http://www.w3.org/2005/Atom" }) + :text_tag("title", payload, { type = "text" }); + end; + }; +}, { + __index = function () return { from_item = tostring }; end; +}); + local pubsub_services = {}; local pubsub_subscribers = {}; local packet_handlers = {}; function handle_packet(session, packet) - module:log("warn", "MQTT packet received! Length: %d", packet.length); + module:log("debug", "MQTT packet received! Length: %d", packet.length); for k,v in pairs(packet) do module:log("debug", "MQTT %s: %s", tostring(k), tostring(v)); end @@ -32,18 +70,26 @@ end function packet_handlers.publish(session, packet) - module:log("warn", "PUBLISH to %s", packet.topic); - local host, node = packet.topic:match("^([^/]+)/(.+)$"); + module:log("info", "PUBLISH to %s", packet.topic); + local host, payload_type, node = packet.topic:match("^([^/]+)/([^/]+)/(.+)$"); + if not host then + module:log("warn", "Invalid topic format - expected: HOST/TYPE/NODE"); + return; + end local pubsub = pubsub_services[host]; if not pubsub then module:log("warn", "Unable to locate host/node: %s", packet.topic); return; end - local id = "mqtt"; - local ok, err = pubsub:publish(node, true, id, - st.stanza("data", { xmlns = "https://prosody.im/protocol/data" }) - :text(packet.data) - ); + + local payload_translator = data_translators[payload_type]; + if not payload_translator or not payload_translator.to_item then + module:log("warn", "Unsupported payload type '%s' on topic '%s'", payload_type, packet.topic); + return; + end + + local payload_item = payload_translator.to_item(packet.data); + local ok, err = pubsub:publish(node, true, payload_item.attr.id, payload_item); if not ok then module:log("warn", "Error publishing MQTT data: %s", tostring(err)); end @@ -51,8 +97,12 @@ function packet_handlers.subscribe(session, packet) for _, topic in ipairs(packet.topics) do - module:log("warn", "SUBSCRIBE to %s", topic); - local host, node = topic:match("^([^/]+)/(.+)$"); + module:log("info", "SUBSCRIBE to %s", topic); + local host, payload_type, node = topic:match("^([^/]+)/([^/]+)/(.+)$"); + if not host then + module:log("warn", "Invalid topic format - expected: HOST/TYPE/NODE"); + return; + end local pubsub = pubsub_subscribers[host]; if not pubsub then module:log("warn", "Unable to locate host/node: %s", topic); @@ -63,8 +113,8 @@ node_subs = {}; pubsub[node] = node_subs; end - session.subscriptions[topic] = true; - node_subs[session] = true; + session.subscriptions[topic] = payload_type; + node_subs[session] = payload_type; end end @@ -116,17 +166,6 @@ listener = mqtt_listener; }); -local function tostring_content(item) - return tostring(item[1]); -end - -local data_translators = setmetatable({ - ["data https://prosody.im/protocol/data"] = tostring_content; - ["json urn:xmpp:json:0"] = tostring_content; -}, { - __index = function () return tostring; end; -}); - function module.add_host(module) local pubsub_module = hosts[module.host].modules.pubsub if pubsub_module then @@ -137,16 +176,22 @@ pubsub_subscribers[module.host] = subscribers; local function handle_publish(event) -- Build MQTT packet - local packet = mqtt.serialize_packet{ - type = "publish"; - id = "\000\000"; - topic = module.host.."/"..event.node; - data = data_translators[tostring(event.item.name).." "..tostring(event.item.attr.xmlns)](event.item); - }; + local packet_types = setmetatable({}, { + __index = function (self, payload_type) + local packet = mqtt.serialize_packet{ + type = "publish"; + id = "\000\000"; + topic = module.host.."/"..payload_type.."/"..event.node; + data = data_translators[payload_type].from_item(event.item) or ""; + }; + rawset(self, packet); + return packet; + end; + }); -- Broadcast to subscribers - module:log("debug", "Broadcasting PUBLISH to subscribers of %s/%s", module.host, event.node); - for session in pairs(subscribers[event.node] or {}) do - session.conn:write(packet); + module:log("debug", "Broadcasting PUBLISH to subscribers of %s/*/%s", module.host, event.node); + for session, payload_type in pairs(subscribers[event.node] or {}) do + session.conn:write(packet_types[payload_type]); module:log("debug", "Sent to %s", tostring(session)); end end
--- a/mod_pubsub_mqtt/mqtt.lib.lua Sat Dec 17 14:13:06 2022 +0100 +++ b/mod_pubsub_mqtt/mqtt.lib.lua Sun Dec 18 15:30:02 2022 +0100 @@ -54,7 +54,7 @@ until bit.band(digit, 0x80) == 0; packet.length = length; if packet.type == "connect" then - if self:read_string() ~= "MQIsdp" then + if self:read_string() ~= "MQTT" then module:log("warn", "Unexpected packet signature!"); packet.type = nil; -- Invalid packet else
--- a/mod_rest/mod_rest.lua Sat Dec 17 14:13:06 2022 +0100 +++ b/mod_rest/mod_rest.lua Sun Dec 18 15:30:02 2022 +0100 @@ -461,9 +461,7 @@ }; }); --- Forward stanzas from XMPP to HTTP and return any reply -local rest_url = module:get_option_string("rest_callback_url", nil); -if rest_url then +function new_webhook(rest_url, send_type) local function get_url() return rest_url; end if rest_url:find("%b{}") then local httputil = require "util.http"; @@ -473,7 +471,6 @@ return render_url(rest_url, { kind = stanza.name, type = at.type, to = at.to, from = at.from }); end end - local send_type = module:get_option_string("rest_callback_content_type", "application/xmpp+xml"); if send_type == "json" then send_type = "application/json"; end @@ -500,7 +497,7 @@ local function handle_stanza(event) local stanza, origin = event.stanza, event.origin; - local reply_allowed = stanza.attr.type ~= "error"; + local reply_allowed = stanza.attr.type ~= "error" and stanza.attr.type ~= "result"; local reply_needed = reply_allowed and stanza.name == "iq"; local receipt; @@ -601,6 +598,16 @@ return true; end + return handle_stanza; +end + +-- Forward stanzas from XMPP to HTTP and return any reply +local rest_url = module:get_option_string("rest_callback_url", nil); +if rest_url then + local send_type = module:get_option_string("rest_callback_content_type", "application/xmpp+xml"); + + local handle_stanza = new_webhook(rest_url, send_type); + local send_kinds = module:get_option_set("rest_callback_stanzas", { "message", "presence", "iq" }); local event_presets = {
--- a/mod_rest/res/openapi.yaml Sat Dec 17 14:13:06 2022 +0100 +++ b/mod_rest/res/openapi.yaml Sun Dec 18 15:30:02 2022 +0100 @@ -1096,6 +1096,7 @@ description: Message ID of a message that has been displayed xml: namespace: urn:xmpp:chat-markers:0 + x_single_attribute: id idle_since: type: string
--- a/mod_rest/res/schema-xmpp.json Sat Dec 17 14:13:06 2022 +0100 +++ b/mod_rest/res/schema-xmpp.json Sun Dec 18 15:30:02 2022 +0100 @@ -733,7 +733,8 @@ "title" : "XEP-0333: Chat Markers", "type" : "string", "xml" : { - "namespace" : "urn:xmpp:chat-markers:0" + "namespace" : "urn:xmpp:chat-markers:0", + "x_single_attribute" : "id" } }, "encryption" : {
--- a/mod_s2soutinjection/mod_s2soutinjection.lua Sat Dec 17 14:13:06 2022 +0100 +++ b/mod_s2soutinjection/mod_s2soutinjection.lua Sun Dec 18 15:30:02 2022 +0100 @@ -1,9 +1,7 @@ local st = require"util.stanza"; -local new_ip = require"util.ip".new_ip; local new_outgoing = require"core.s2smanager".new_outgoing; local bounce_sendq = module:depends"s2s".route_to_new_session.bounce_sendq; local initialize_filters = require "util.filters".initialize; -local st = require "util.stanza"; local portmanager = require "core.portmanager"; @@ -17,7 +15,7 @@ -- The proxy_listener handles connection while still connecting to the proxy, -- then it hands them over to the normal listener (in mod_s2s) -local proxy_listener = { default_port = port, default_mode = "*a", default_interface = "*" }; +local proxy_listener = { default_port = nil, default_mode = "*a", default_interface = "*" }; function proxy_listener.onconnect(conn) local session = sessions[conn]; @@ -25,7 +23,7 @@ -- Now the real s2s listener can take over the connection. local listener = portmanager.get_service("s2s").listener; - local w, log = conn.send, session.log; + local log = session.log; local filter = initialize_filters(session); @@ -71,13 +69,13 @@ local from_host, to_host, stanza = event.from_host, event.to_host, event.stanza; local inject = injected and injected[to_host]; if not inject then return end - log("debug", "opening a new outgoing connection for this stanza"); + module:log("debug", "opening a new outgoing connection for this stanza"); local host_session = new_outgoing(from_host, to_host); -- Store in buffer host_session.bounce_sendq = bounce_sendq; host_session.sendq = { {tostring(stanza), stanza.attr.type ~= "error" and stanza.attr.type ~= "result" and st.reply(stanza)} }; - log("debug", "stanza [%s] queued until connection complete", tostring(stanza.name)); + host_session.log("debug", "stanza [%s] queued until connection complete", tostring(stanza.name)); local host, port = inject[1] or inject, tonumber(inject[2]) or 5269;
--- a/mod_sasl2/README.md Sat Dec 17 14:13:06 2022 +0100 +++ b/mod_sasl2/README.md Sun Dec 18 15:30:02 2022 +0100 @@ -1,10 +1,18 @@ --- labels: -- Stage-Alpha +- Stage-Beta summary: "XEP-0388: Extensible SASL Profile" --- -Experimental implementation of [XEP-0388: Extensible SASL Profile] +Implementation of [XEP-0388: Extensible SASL Profile]. **Note: At the time of +writing (Nov 2022) the version of the XEP implemented by this module is still +working its way through the XSF standards process. See [PR #1214](https://github.com/xsf/xeps/pull/1214) +for the current status.** + +## Configuration + +This module honours the same configuration options as Prosody's existing +[mod_saslauth](https://prosody.im/doc/modules/mod_saslauth). ## Developers
--- a/mod_sasl2/mod_sasl2.lua Sat Dec 17 14:13:06 2022 +0100 +++ b/mod_sasl2/mod_sasl2.lua Sun Dec 18 15:30:02 2022 +0100 @@ -18,6 +18,7 @@ local xmlns_sasl2 = "urn:xmpp:sasl:2"; +local secure_auth_only = module:get_option_boolean("c2s_require_encryption", module:get_option_boolean("require_encryption", true)); local allow_unencrypted_plain_auth = module:get_option_boolean("allow_unencrypted_plain_auth", false) local insecure_mechanisms = module:get_option_set("insecure_sasl_mechanisms", allow_unencrypted_plain_auth and {} or {"PLAIN", "LOGIN"}); local disabled_mechanisms = module:get_option_set("disable_sasl_mechanisms", { "DIGEST-MD5" }); @@ -44,6 +45,9 @@ if origin.type ~= "c2s_unauthed" then log("debug", "Already authenticated"); return + elseif secure_auth_only and not origin.secure then + log("debug", "Not offering authentication on insecure connection"); + return; end local sasl_handler = usermanager_get_sasl_handler(host, origin) @@ -187,6 +191,9 @@ end module:hook_tag(xmlns_sasl2, "authenticate", function (session, auth) + if secure_auth_only and not session.secure then + return handle_status(session, "failure", "encryption-required"); + end local sasl_handler = session.sasl_handler; if not sasl_handler then sasl_handler = usermanager_get_sasl_handler(host, session);
--- a/mod_sasl2_bind2/README.md Sat Dec 17 14:13:06 2022 +0100 +++ b/mod_sasl2_bind2/README.md Sun Dec 18 15:30:02 2022 +0100 @@ -1,7 +1,16 @@ --- labels: -- Stage-Alpha +- Stage-Beta summary: "Bind 2 integration with SASL2" +rockspec: + dependencies: + - mod_sasl2 --- -Add support for inlining Bind 2.0 into the SASL2 process. Experimental WIP. +Add support for [XEP-0386: Bind 2], which is a new method for clients to bind +resources and establish sessions in XMPP, using SASL2. **Note: At the time of +writing (November 2022), this plugin implements a version of XEP-0386 that is +still working its way through the XSF standards process. See [PR #1217](https://github.com/xsf/xeps/pull/1217) +for more information and current status.** + +This module depends on [mod_sasl2]. It exposes no configuration options.
--- a/mod_sasl2_bind2/mod_sasl2_bind2.lua Sat Dec 17 14:13:06 2022 +0100 +++ b/mod_sasl2_bind2/mod_sasl2_bind2.lua Sun Dec 18 15:30:02 2022 +0100 @@ -8,6 +8,8 @@ local xmlns_bind2 = "urn:xmpp:bind:0"; local xmlns_sasl2 = "urn:xmpp:sasl:2"; +module:depends("sasl2"); + -- Advertise what we can do module:hook("advertise-sasl-features", function(event)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_sasl2_fast/README.md Sun Dec 18 15:30:02 2022 +0100 @@ -0,0 +1,34 @@ +--- +labels: +- Stage-Beta +summary: "Fast Authentication Streamlining Tokens" +rockspec: + dependencies: + - mod_sasl2 +--- + +This module implements a mechanism via which clients can exchange a password +for a secure token, improving security and streamlining future reconnections. + +At the time of writing, the XEP that describes the FAST protocol is still +working its way through the XSF standards process. You can [view the FAST XEP +proposal here](https://xmpp.org/extensions/inbox/xep-fast.html). + +This module depends on [mod_sasl2]. + +## Configuration + +| Name | Description | Default | +|---------------------------|--------------------------------------------------------|-----------------------| +| sasl2_fast_token_ttl | Default token expiry (seconds) | `86400*21` (21 days) | +| sasl2_fast_token_min_ttl | Time before tokens are eligible for rotation (seconds) | `86400` (1 day) | + +The `sasl2_fast_token_ttl` option determines the length of time a client can +remain disconnected before being "logged out" and needing to authenticate with +a password. Clients must perform at least one FAST authentication within this +period to remain active. + +The `sasl2_fast_token_min_ttl` option defines how long before a token will be +rotated by the server. By default a token is rotated if it is older than 24 +hours. This value should be less than `sasl2_fast_token_ttl` to prevent +clients being logged out unexpectedly.
--- a/mod_sasl2_fast/mod_sasl2_fast.lua Sat Dec 17 14:13:06 2022 +0100 +++ b/mod_sasl2_fast/mod_sasl2_fast.lua Sun Dec 18 15:30:02 2022 +0100 @@ -6,6 +6,8 @@ local now = require "util.time".now; local hash = require "util.hashes"; +module:depends("sasl2"); + -- Tokens expire after 21 days by default local fast_token_ttl = module:get_option_number("sasl2_fast_token_ttl", 86400*21); -- Tokens are automatically rotated daily @@ -47,6 +49,7 @@ if hash.equals(expected_hash, token_hash) then local current_time = now(); if token.expires_at < current_time then + log("debug", "Token found, but it has expired (%ds ago). Cleaning up...", current_time - token.expires_at); token_store:set(username, key, nil); return nil, "credentials-expired"; end @@ -61,9 +64,10 @@ if invalidate then token_store:set(username, key, nil); elseif current_time - token.issued_at > fast_token_min_ttl then + log("debug", "FAST token due for rotation (age: %d)", current_time - token.issued_at); rotation_needed = true; end - return true, username, hmac_f(token.secret, "Responder"..cb_data), token, rotation_needed; + return true, username, hmac_f(token.secret, "Responder"..cb_data), rotation_needed; end end if not tried_current_token then @@ -98,6 +102,8 @@ end local sasl_handler = get_sasl_handler(username); if not sasl_handler then return; end + sasl_handler.profile.cb = session.sasl_handler.profile.cb; + sasl_handler.userdata = session.sasl_handler.userdata; session.fast_sasl_handler = sasl_handler; local fast = st.stanza("fast", { xmlns = xmlns_fast }); for mech in pairs(sasl_handler:mechanisms()) do @@ -150,7 +156,7 @@ local token_request = session.fast_token_request; local client_id = session.client_id; local sasl_handler = session.sasl_handler; - if token_request or sasl_handler.fast and sasl_handler.rotation_needed then + if token_request or (sasl_handler.fast and sasl_handler.rotation_needed) then if not client_id then session.log("warn", "FAST token requested, but missing client id"); return; @@ -173,23 +179,24 @@ local function new_ht_mechanism(mechanism_name, backend_profile_name, cb_name) return function (sasl_handler, message) local backend = sasl_handler.profile[backend_profile_name]; - local username, token_hash = message:match("^([^%z]+)%z(.+)$"); - if not username then + local authc_username, token_hash = message:match("^([^%z]+)%z(.+)$"); + if not authc_username then return "failure", "malformed-request"; end local cb_data = cb_name and sasl_handler.profile.cb[cb_name](sasl_handler) or ""; - local ok, status, response, rotation_needed = backend( + local ok, authz_username, response, rotation_needed = backend( mechanism_name, - username, + authc_username, sasl_handler.client_id, token_hash, cb_data, sasl_handler.invalidate ); if not ok then - return "failure", status or "not-authorized"; + -- authz_username is error condition + return "failure", authz_username or "not-authorized"; end - sasl_handler.username = status; + sasl_handler.username = authz_username; sasl_handler.rotation_needed = rotation_needed; return "success", response; end @@ -201,10 +208,10 @@ backend_profile_name, cb_name ), - { cb_name }); + cb_name and { cb_name } or nil); end register_ht_mechanism("HT-SHA-256-NONE", "ht_sha_256", nil); register_ht_mechanism("HT-SHA-256-UNIQ", "ht_sha_256", "tls-unique"); -register_ht_mechanism("HT-SHA-256-ENDP", "ht_sha_256", "tls-endpoint"); +register_ht_mechanism("HT-SHA-256-ENDP", "ht_sha_256", "tls-server-end-point"); register_ht_mechanism("HT-SHA-256-EXPR", "ht_sha_256", "tls-exporter");
--- a/mod_sasl2_sm/README.md Sat Dec 17 14:13:06 2022 +0100 +++ b/mod_sasl2_sm/README.md Sun Dec 18 15:30:02 2022 +0100 @@ -1,7 +1,17 @@ --- labels: -- Stage-Alpha +- Stage-Beta summary: "XEP-0198 integration with SASL2" +rockspec: + dependencies: + - mod_sasl2 --- Add support for inlining stream management negotiation into the SASL2 process. + +**Note: At the time of writing (November 2022), this module implements a +version of XEP-0198 that is still working its way through the XSF standards +process. For more information and current status, see [PR #1215](https://github.com/xsf/xeps/pull/1215).** + +This module depends on [mod_sasl2] and [mod_sasl2_bind2]. It exposes no +configuration options.
--- a/mod_sasl2_sm/mod_sasl2_sm.lua Sat Dec 17 14:13:06 2022 +0100 +++ b/mod_sasl2_sm/mod_sasl2_sm.lua Sun Dec 18 15:30:02 2022 +0100 @@ -5,6 +5,8 @@ local xmlns_sasl2 = "urn:xmpp:sasl:2"; local xmlns_sm = "urn:xmpp:sm:3"; +module:depends("sasl2"); + -- Advertise what we can do module:hook("advertise-sasl-features", function (event)
--- a/mod_vjud/README.markdown Sat Dec 17 14:13:06 2022 +0100 +++ b/mod_vjud/README.markdown Sun Dec 18 15:30:02 2022 +0100 @@ -40,7 +40,7 @@ Option Default Description ------------ ---------- -------------------------------- - vjud\_mode "opt-in" Defines how the module behaves + vjud\_mode "opt-in" Choose how users are listed in the directory ("opt-in" or "all") Compatibility =============