Software / code / prosody-modules
Comparison
mod_debug_omemo/mod_debug_omemo.lua @ 4682:e4e5474420e6
mod_debug_omemo: OMEMO debugging tool
| author | Matthew Wild <mwild1@gmail.com> |
|---|---|
| date | Mon, 13 Sep 2021 19:24:13 +0100 |
| child | 4685:07b6f444bafb |
comparison
equal
deleted
inserted
replaced
| 4681:edbd84bbd8fb | 4682:e4e5474420e6 |
|---|---|
| 1 local array = require "util.array"; | |
| 2 local jid = require "util.jid"; | |
| 3 local set = require "util.set"; | |
| 4 local st = require "util.stanza"; | |
| 5 local url_escape = require "util.http".urlencode; | |
| 6 | |
| 7 local base_url = "https://"..module.host.."/"; | |
| 8 | |
| 9 local render_html_template = require"util.interpolation".new("%b{}", st.xml_escape, { | |
| 10 urlescape = url_escape; | |
| 11 lower = string.lower; | |
| 12 classname = function (s) return (s:gsub("%W+", "-")); end; | |
| 13 relurl = function (s) | |
| 14 if s:match("^%w+://") then | |
| 15 return s; | |
| 16 end | |
| 17 return base_url.."/"..s; | |
| 18 end; | |
| 19 }); | |
| 20 local render_url = require "util.interpolation".new("%b{}", url_escape, { | |
| 21 urlescape = url_escape; | |
| 22 noscheme = function (url) | |
| 23 return (url:gsub("^[^:]+:", "")); | |
| 24 end; | |
| 25 }); | |
| 26 | |
| 27 local mod_pep = module:depends("pep"); | |
| 28 | |
| 29 local mam = module:open_store("archive", "archive"); | |
| 30 | |
| 31 local function get_user_omemo_info(username) | |
| 32 local everything_valid = true; | |
| 33 local any_device = false; | |
| 34 local omemo_status = {}; | |
| 35 local omemo_devices; | |
| 36 local pep_service = mod_pep.get_pep_service(username); | |
| 37 if pep_service and pep_service.nodes then | |
| 38 local ok, _, device_list = pep_service:get_last_item("eu.siacs.conversations.axolotl.devicelist", true); | |
| 39 if ok and device_list then | |
| 40 device_list = device_list:get_child("list", "eu.siacs.conversations.axolotl"); | |
| 41 end | |
| 42 if device_list then | |
| 43 omemo_devices = {}; | |
| 44 for device_entry in device_list:childtags("device") do | |
| 45 any_device = true; | |
| 46 local device_info = {}; | |
| 47 local device_id = tonumber(device_entry.attr.id or ""); | |
| 48 if device_id then | |
| 49 device_info.id = device_id; | |
| 50 local bundle_id = ("eu.siacs.conversations.axolotl.bundles:%d"):format(device_id); | |
| 51 local have_bundle, _, bundle = pep_service:get_last_item(bundle_id, true); | |
| 52 if have_bundle and bundle and bundle:get_child("bundle", "eu.siacs.conversations.axolotl") then | |
| 53 device_info.have_bundle = true; | |
| 54 local config_ok, bundle_config = pep_service:get_node_config(bundle_id, true); | |
| 55 if config_ok and bundle_config then | |
| 56 device_info.bundle_config = bundle_config; | |
| 57 if bundle_config.max_items == 1 | |
| 58 and bundle_config.access_model == "open" | |
| 59 and bundle_config.persist_items == true | |
| 60 and bundle_config.publish_model == "publishers" then | |
| 61 device_info.valid = true; | |
| 62 end | |
| 63 end | |
| 64 end | |
| 65 end | |
| 66 if device_info.valid == nil then | |
| 67 device_info.valid = false; | |
| 68 everything_valid = false; | |
| 69 end | |
| 70 table.insert(omemo_devices, device_info); | |
| 71 end | |
| 72 | |
| 73 local config_ok, list_config = pep_service:get_node_config("eu.siacs.conversations.axolotl.devicelist", true); | |
| 74 if config_ok and list_config then | |
| 75 omemo_status.config = list_config; | |
| 76 if list_config.max_items == 1 | |
| 77 and list_config.access_model == "open" | |
| 78 and list_config.persist_items == true | |
| 79 and list_config.publish_model == "publishers" then | |
| 80 omemo_status.config_valid = true; | |
| 81 end | |
| 82 end | |
| 83 if omemo_status.config_valid == nil then | |
| 84 omemo_status.config_valid = false; | |
| 85 everything_valid = false; | |
| 86 end | |
| 87 end | |
| 88 end | |
| 89 omemo_status.valid = everything_valid and any_device; | |
| 90 return { | |
| 91 status = omemo_status; | |
| 92 devices = omemo_devices; | |
| 93 }; | |
| 94 end | |
| 95 | |
| 96 local access_model_text = { | |
| 97 open = "Public"; | |
| 98 whitelist = "Private"; | |
| 99 roster = "Contacts only"; | |
| 100 presence = "Contacts only"; | |
| 101 }; | |
| 102 | |
| 103 local function render_message(event, path) | |
| 104 local username, message_id = path:match("^([^/]+)/(.+)$"); | |
| 105 if not username then | |
| 106 return 400; | |
| 107 end | |
| 108 local message; | |
| 109 for _, result in mam:find(username, { key = message_id }) do | |
| 110 message = result; | |
| 111 end | |
| 112 if not message then | |
| 113 return 404; | |
| 114 end | |
| 115 | |
| 116 local user_omemo_status = get_user_omemo_info(username); | |
| 117 | |
| 118 local user_rids = set.new(array.pluck(user_omemo_status.devices or {}, "id")) / tostring; | |
| 119 | |
| 120 local message_omemo_header = message:find("{eu.siacs.conversations.axolotl}encrypted/header"); | |
| 121 local message_rids = set.new(); | |
| 122 local rid_info = {}; | |
| 123 if message_omemo_header then | |
| 124 for key_el in message_omemo_header:childtags("key") do | |
| 125 local rid = key_el.attr.rid; | |
| 126 if rid then | |
| 127 message_rids:add(rid); | |
| 128 local prekey = key_el.attr.prekey; | |
| 129 rid_info = { | |
| 130 prekey = prekey and (prekey == "1" or prekey:lower() == "true"); | |
| 131 }; | |
| 132 end | |
| 133 end | |
| 134 end | |
| 135 | |
| 136 local rids = user_rids + message_rids; | |
| 137 | |
| 138 local direction = jid.bare(message.attr.to) == (username.."@"..module.host) and "incoming" or "outgoing"; | |
| 139 | |
| 140 local is_encrypted = not not message_omemo_header; | |
| 141 | |
| 142 local sender_id = message_omemo_header and message_omemo_header.attr.sid or nil; | |
| 143 | |
| 144 local f = module:load_resource("view.tpl.html"); | |
| 145 if not f then | |
| 146 return 500; | |
| 147 end | |
| 148 local tpl = f:read("*a"); | |
| 149 | |
| 150 local data = { user = username, rids = {} }; | |
| 151 for rid in rids do | |
| 152 data.rids[rid] = { | |
| 153 status = message_rids:contains(rid) and "Encrypted" or user_rids:contains(rid) and "Missing" or nil; | |
| 154 prekey = rid_info.prekey; | |
| 155 }; | |
| 156 end | |
| 157 | |
| 158 data.message = { | |
| 159 type = message.attr.type or "normal"; | |
| 160 direction = direction; | |
| 161 encryption = is_encrypted and "encrypted" or "unencrypted"; | |
| 162 }; | |
| 163 | |
| 164 data.omemo = { | |
| 165 sender_id = sender_id; | |
| 166 status = user_omemo_status.status.valid and "no known issues" or "problems"; | |
| 167 }; | |
| 168 | |
| 169 data.omemo.devices = {}; | |
| 170 for _, device_info in ipairs(user_omemo_status.devices) do | |
| 171 data.omemo.devices[("%d"):format(device_info.id)] = { | |
| 172 status = device_info.valid and "OK" or "Problem"; | |
| 173 bundle = device_info.have_bundle and "Published" or "Missing"; | |
| 174 access_model = access_model_text[device_info.bundle_config and device_info.bundle_config.access_model or nil]; | |
| 175 }; | |
| 176 end | |
| 177 | |
| 178 event.response.headers.content_type = "text/html; charset=utf-8"; | |
| 179 return render_html_template(tpl, data); | |
| 180 end | |
| 181 | |
| 182 local function check_omemo_fallback(event) | |
| 183 local message = event.stanza; | |
| 184 | |
| 185 local message_omemo_header = message:find("{eu.siacs.conversations.axolotl}encrypted/header"); | |
| 186 if not message_omemo_header then return; end | |
| 187 | |
| 188 local to_bare = jid.bare(message.attr.to); | |
| 189 | |
| 190 local archive_stanza_id; | |
| 191 for stanza_id_tag in message:childtags("stanza-id", "urn:xmpp:sid:0") do | |
| 192 if stanza_id_tag.attr.by == to_bare then | |
| 193 archive_stanza_id = stanza_id_tag.attr.id; | |
| 194 end | |
| 195 end | |
| 196 if not archive_stanza_id then | |
| 197 return; | |
| 198 end | |
| 199 | |
| 200 local debug_url = render_url(module:http_url().."/view/{username}/{message_id}", { | |
| 201 username = jid.node(to_bare); | |
| 202 message_id = archive_stanza_id; | |
| 203 }); | |
| 204 | |
| 205 local body = message:get_child("body"); | |
| 206 if not body then | |
| 207 body = st.stanza("body") | |
| 208 :text("This message is encrypted using OMEMO, but could not be decrypted by your device.\nFor more information see: "..debug_url); | |
| 209 message:reset():add_child(body); | |
| 210 else | |
| 211 body:text("\n\nOMEMO debug information: "..debug_url); | |
| 212 end | |
| 213 end | |
| 214 | |
| 215 module:hook("message/bare", check_omemo_fallback, 1); | |
| 216 module:hook("message/full", check_omemo_fallback, 1); | |
| 217 | |
| 218 module:depends("http") | |
| 219 module:provides("http", { | |
| 220 route = { | |
| 221 ["GET /view/*"] = render_message; | |
| 222 }; | |
| 223 }); |