Mercurial > prosody-modules
view mod_http_presence/mod_http_presence.lua @ 6319:04c3273cb81f
mod_auth_cyrus: Add empty 'profile' table to SASL handler objects
This is for compatibility with Prosody's built-in util.sasl objects.
A SASL profile table usually includes methods supported by the backend, which
can be used by SASL mechanism handlers to perform operations (such as testing
the password). It also optionally contains a 'cb' field with channel binding
method handlers.
The Cyrus backend doesn't support channel binding, and doesn't have the same
concept of auth backend methods (it handles all that internally, and Prosody
has no insight or control over it).
Thus, we create an empty profile which informs Prosody that the SASL handler
does not support any of the auth or channel binding methods. Some features
will not work, but they didn't work anyway. This just makes it explicit.
This fixes a traceback in mod_sasl2_fast, which expected SASL handlers to
always contain a 'profile' field.
| author | Matthew Wild <mwild1@gmail.com> |
|---|---|
| date | Thu, 04 Sep 2025 10:14:46 +0100 |
| parents | 9dcdb56f75dd |
| children |
line wrap: on
line source
local mod_pep = module:depends("pep"); module:depends("http"); local storagemanager = require "core.storagemanager"; local usermanager = require "core.usermanager"; local stanza = require "util.stanza".stanza; local deserialize = require "util.stanza".deserialize; local base64_decode = require "util.encodings".base64.decode; local base64_encode = require "util.encodings".base64.encode; local http = require "net.http"; local jid = require "util.jid"; function get_user_presence(bare_jid) local host = jid.host(bare_jid); local sessions = prosody.hosts[host] and prosody.hosts[host].sessions[jid.node(bare_jid)]; if not sessions then return { status = "offline", message = nil }; end local highest_priority_session = nil; local highest_priority = -math.huge; for resource, session in pairs(sessions.sessions) do if session.presence then local priority = session.priority or 0; if priority > highest_priority then highest_priority = priority; highest_priority_session = session; end end end if not highest_priority_session then return { status = "offline", message = nil }; end local presence = highest_priority_session.presence; return { status = presence and (presence:get_child("show") and presence:get_child("show"):get_text() or "online") or "offline", message = presence and presence:get_child("status") and presence:get_child("status"):get_text() or nil }; end function get_user_avatar(bare_jid) local pep_service = mod_pep.get_pep_service(jid.node(bare_jid)); if not pep_service then module:log("error", "PEP storage not available"); return nil; end local meta_ok, hash, meta = pep_service:get_last_item("urn:xmpp:avatar:metadata", module.host); if not meta_ok or not hash then module:log("debug", "Failed to get avatar metadata for %s: %s", bare_jid, "Not OK"); return nil; end local data_ok, data_hash, data = pep_service:get_last_item("urn:xmpp:avatar:data", module.host, hash); local data_err = nil; if not data_ok then data_err = "Not OK"; elseif data_hash ~= hash then data_err = "Hash does not match"; elseif type(data) ~= "table" then data_err = "Data of type table"; end if data_err then module:log("debug", "Failed to get avatar data for %s, hash %s: %s", bare_jid, hash, data_err); return nil; end local info = meta.tags[1]:get_child("info"); if not info then module:log("debug", "Missing avatar info for %s, hash %s", bare_jid, hash); return nil; end return info and info.attr.type or "application/octet-stream", data[1]:get_text(); end function get_user_nickname(bare_jid) local pep_service = mod_pep.get_pep_service(jid.node(bare_jid)); if not pep_service then module:log("error", "PEP storage not available"); return nil; end local ok, nick, nick_item = pep_service:get_last_item("urn:xmpp:vcard4", module.host); if not ok then module:log("debug", "Failed to get nick for %s: %s", bare_jid, "Not OK"); return nil; end if nick_item and nick_item.tags and nick_item.tags[1] and nick_item.tags[1].tags then for _, tag in ipairs(nick_item.tags[1].tags) do if tag.name == "nickname" and tag.tags and tag.tags[1] and tag.tags[1][1] then nickname = tag.tags[1][1]; module:log("debug", "Nickname found for JID %s: %s", bare_jid, nickname); return nickname; end end else module:log("debug", "Invalid vCard4 item structure for JID %s", bare_jid); return nil; end module:log("debug", "No <nickname> element in vCard4 for JID %s", bare_jid); return jid.node(bare_jid); end function get_muc_avatar(bare_jid) local node = jid.node(bare_jid); local vcard_store = storagemanager.open(module.host, "vcard_muc") if not vcard_store then module:log("error", "MUC vCard store not available for host: %s", module.host); return nil, nil, "MUC vCard store not available"; end local vcard_data, err = vcard_store:get(node); if not vcard_data then module:log("debug", "No vCard data for MUC %s: %s", bare_jid, err or "No data"); return nil, nil, err or "No vCard data"; end local vcard = deserialize(vcard_data); if not vcard then module:log("debug", "Failed to parse vCard for MUC %s", bare_jid); return nil, nil, "Failed to parse vCard"; end local photo = vcard:get_child("PHOTO"); if not photo then module:log("debug", "No <PHOTO> element in vCard for MUC %s", bare_jid); return nil, nil, "No photo element"; end local content_type = photo:get_child_text("TYPE") or "application/octet-stream"; local avatar_data = photo:get_child_text("BINVAL"); if not avatar_data then module:log("debug", "No <BINVAL> in <PHOTO> for MUC %s", bare_jid); return nil, nil, "No avatar data"; end module:log("debug", "MUC avatar found for JID %s: type=%s, data=%s", bare_jid, content_type, avatar_data:sub(1, 20) .. "..."); return content_type, avatar_data, nil; end function get_muc_info(bare_jid) local node = jid.node(bare_jid); local muc_store = storagemanager.open(module.host, "config"); if not muc_store then module:log("error", "MUC config store not available for host: %s", module.host); return nil, nil, "MUC config store not available"; end local config_data, err = muc_store:get(node); if not config_data then module:log("debug", "No config data for JID %s: %s", bare_jid, err or "No data"); return nil, nil, err or "No config data"; end local muc_name = config_data._data and config_data._data.name; local muc_description = config_data._data and config_data._data.description; if not muc_name and not muc_description then module:log("debug", "No name or description in config for JID %s", bare_jid); return nil, nil, "No name or description"; end module:log("debug", "MUC info for JID %s: name=%s, desc=%s", bare_jid, muc_name, muc_description); return muc_name, muc_description, nil; end function get_muc_users(bare_jid) local component = hosts[module.host]; if not component then module:log("error", "No component found for host: %s", module.host); return nil, "No MUC component found"; end local muc = component.modules.muc; if not muc then module:log("error", "MUC module not loaded for host: %s", module.host); return nil, "MUC module not loaded"; end local room = muc.get_room_from_jid(bare_jid); if not room then module:log("error", "Room %s does not exist", bare_jid); return nil, "Room does not exist"; end local count = 0; for _ in room:each_occupant() do count = count + 1; end module:log("debug", "Room %s has %d occupants", bare_jid, count); return count, nil; end function serve_user(response, format, user_jid) local presence = get_user_presence(user_jid); local nickname = get_user_nickname(user_jid) or user_jid; local status = presence.status or "offline"; local message = presence.message or ""; if not format or format == "" or format == "full" then response.headers["Content-Type"] = "text/html"; return response:send( [[<!DOCTYPE html>]].. tostring( stanza("html") :tag("head") :tag("title"):text(nickname):up() :tag("link", { rel = "stylesheet", href = "data:text/css;base64,"..base64_encode(request_resource("style.css")) }) :up() :tag("body") :tag("table", { width = "100%" }) :tag("colgroup") :tag("col", { width = "64px" }):up() :tag("col"):up() :up() :tag("tr") :tag("td", { rowspan = "3", valign = "top" }) :tag("img", { id = "avatar", src = "./avatar", width = "64" }) :up() :tag("td") :tag("img", { id = "status-icon", src = "./status-icon", title = status, alt = "("..status..")" }):up() :tag("b", { id = "nickname"}):text(" "..nickname):up() :up() :up() :tag("tr") :tag("td", { id = "msg-cell" }):text(message):up() :up() :tag("tr") :tag("td", { id = "jid-cell" }) :tag("i") :tag("a", { href = "xmpp:"..user_jid.."?add" }):text(user_jid):up() :up() :up() :up() ) ); elseif format == "nickname" then response.headers["Content-Type"] = "text/plain"; return response:send(nickname); elseif format == "status" then response.headers["Content-Type"] = "text/plain"; return response:send(status); elseif format == "message" then response.headers["Content-Type"] = "text/plain"; return response:send(message); elseif format == "status-icon" then response.headers["Content-Type"] = "image/png"; local status_resource = request_resource(status..".png"); if not status_resource then return response:send(request_resource("offline.png")); end return response:send(status_resource); elseif format == "avatar" then local avatar_mime, avatar_data = get_user_avatar(user_jid); if not avatar_mime or not avatar_data then response.headers["Content-Type"] = "image/png"; return response:send(request_resource("avatar.png")); end response.headers["Content-Type"] = avatar_mime; return response:send(base64_decode(avatar_data)); else response.headers["Content-Type"] = "text/plain"; return response:send(status..": "..message); end end function serve_muc(response, format, muc_jid) local muc_name, muc_desc, err = get_muc_info(muc_jid); local muc_users, _ = get_muc_users(muc_jid); if not format or format == "" or format == "full" then response.headers["Content-Type"] = "text/html"; return response:send( [[<!DOCTYPE html>]].. tostring( stanza("html") :tag("head") :tag("title"):text(muc_name or muc_jid):up() :tag("link", { rel = "stylesheet", href = "data:text/css;base64,"..base64_encode(request_resource("style.css")) }) :up() :tag("body") :tag("table", { width = "100%" }) :tag("colgroup") :tag("col", { width = "64px" }):up() :tag("col"):up() :up() :tag("tr") :tag("td", { rowspan = "3", valign = "top" }) :tag("img", { id = "avatar", src = "./avatar", width = "64" }) :up() :tag("td") :tag("img", { id = "status-icon", src = "./status-icon", title = "muc", alt = "(muc)" }):up() :tag("b", { id = "nickname" }):text(" "..(muc_name or muc_jid)):up() :tag("a", { id = "muc-users" }):text(" ("..muc_users.." users)"):up() :up() :up() :tag("tr") :tag("td", { id = "msg-cell" }):text(muc_desc):up() :up() :tag("tr") :tag("td", { id = "jid-cell" }) :tag("i") :tag("a", { href = "xmpp:"..muc_jid.."?join" }):text(muc_jid):up() :up() :up() :up() ) ); elseif format == "users" then response.headers["Content-Type"] = "text/plain"; return response:send(muc_users.." users"); elseif format == "name" then response.headers["Content-Type"] = "text/plain"; return response:send(muc_name); elseif format == "status" then response.headers["Content-Type"] = "text/plain"; return response:send("muc"); elseif format == "description" then response.headers["Content-Type"] = "text/plain"; return response:send(muc_desc); elseif format == "status-icon" then response.headers["Content-Type"] = "image/png"; return response:send(request_resource("muc.png")); elseif format == "avatar" then local avatar_mime, avatar_data = get_muc_avatar(muc_jid); if not avatar_mime or not avatar_data then response.headers["Content-Type"] = "image/png"; return response:send(request_resource("avatar.png")); end response.headers["Content-Type"] = avatar_mime; return response:send(base64_decode(avatar_data)); else response.headers["Content-Type"] = "text/plain"; return response:send((muc_name or muc_jid)..": "..(muc_desc or "")); end end function request_resource(name) local resource_path = module:get_option_string("presence_resource_path", "resources"); local i, err = module:load_resource(resource_path.."/"..name); if not i then module:log("warn", "Failed to open resource file %s: %s", resource_path.."/"..name, err); return ""; end return i:read("*a"); end function handle_request(event, path) local request = event.request; local response = event.response; local name, format = path:match("^([%w-_\\.]+)/(.*)$"); module:log("debug", "loading format '%s' for jid %s", format or "standard", name); if not name then response.status_code = 404; return response:send("Missing JID"); end local bare_jid = jid.join(name, module.host, nil); local component = hosts[module.host]; if component.type == "component" and component.modules.muc then local muc = component.modules.muc; if not muc.get_room_from_jid(bare_jid) then response.status_code = 404; return response:send("MUC does not exist"); end return serve_muc(response, format or "full", bare_jid); else if not usermanager.user_exists(name, module.host) then response.status_code = 404; return response:send("User does not exist"); end return serve_user(response, format or "full", bare_jid); end end module:provides("http", { default_path = module:get_option_string("presence_http_path", "/presence"); route = { ["GET /*"] = handle_request; }; });
