Comparison

mod_cloud_notify/mod_cloud_notify.lua @ 4295:d44a8d3dd571

mod_cloud_notify: Some code cleanup, now luacheck-clean. No functionality changes.
author Matthew Wild <mwild1@gmail.com>
date Fri, 11 Dec 2020 07:03:31 +0000
parent 4273:8bf83e883593
child 4324:45dcf5d4cd6c
comparison
equal deleted inserted replaced
4294:ae8191d9d533 4295:d44a8d3dd571
2 -- Copyright (C) 2015-2016 Kim Alvefur 2 -- Copyright (C) 2015-2016 Kim Alvefur
3 -- Copyright (C) 2017-2019 Thilo Molitor 3 -- Copyright (C) 2017-2019 Thilo Molitor
4 -- 4 --
5 -- This file is MIT/X11 licensed. 5 -- This file is MIT/X11 licensed.
6 6
7 local t_insert = table.insert;
8 local s_match = string.match;
9 local s_sub = string.sub;
10 local os_time = os.time; 7 local os_time = os.time;
11 local next = next;
12 local st = require"util.stanza"; 8 local st = require"util.stanza";
13 local jid = require"util.jid"; 9 local jid = require"util.jid";
14 local dataform = require"util.dataforms".new; 10 local dataform = require"util.dataforms".new;
15 local filters = require"util.filters";
16 local hashes = require"util.hashes"; 11 local hashes = require"util.hashes";
17 local random = require"util.random"; 12 local random = require"util.random";
18 local cache = require"util.cache"; 13 local cache = require"util.cache";
19 14
20 local xmlns_push = "urn:xmpp:push:0"; 15 local xmlns_push = "urn:xmpp:push:0";
35 -- This uses util.cache for caching the most recent devices and removing all old devices when max_push_devices is reached 30 -- This uses util.cache for caching the most recent devices and removing all old devices when max_push_devices is reached
36 local push_store = (function() 31 local push_store = (function()
37 local store = module:open_store(); 32 local store = module:open_store();
38 local push_services = {}; 33 local push_services = {};
39 local api = {}; 34 local api = {};
35 --luacheck: ignore 212/self
40 function api:get(user) 36 function api:get(user)
41 if not push_services[user] then 37 if not push_services[user] then
42 local loaded, err = store:get(user); 38 local loaded, err = store:get(user);
43 if not loaded and err then 39 if not loaded and err then
44 module:log("warn", "Error reading push notification storage for user '%s': %s", user, tostring(err)); 40 module:log("warn", "Error reading push notification storage for user '%s': %s", user, tostring(err));
83 local identifier = id2identifier[stanza.attr.id]; 79 local identifier = id2identifier[stanza.attr.id];
84 if node == nil then return false; end -- unknown stanza? Ignore for now! 80 if node == nil then return false; end -- unknown stanza? Ignore for now!
85 local from = stanza.attr.from; 81 local from = stanza.attr.from;
86 local user_push_services = push_store:get(node); 82 local user_push_services = push_store:get(node);
87 local changed = false; 83 local changed = false;
88 84
89 for push_identifier, _ in pairs(user_push_services) do 85 for push_identifier, _ in pairs(user_push_services) do
90 if push_identifier == identifier then 86 if push_identifier == identifier then
91 if user_push_services[push_identifier] and user_push_services[push_identifier].jid == from and error_type ~= "wait" then 87 if user_push_services[push_identifier] and user_push_services[push_identifier].jid == from and error_type ~= "wait" then
92 push_errors[push_identifier] = push_errors[push_identifier] + 1; 88 push_errors[push_identifier] = push_errors[push_identifier] + 1;
93 module:log("info", "Got error of type '%s' (%s) for identifier '%s': " 89 module:log("info", "Got error of type '%s' (%s) for identifier '%s': "
132 local node = id2node[stanza.attr.id]; 128 local node = id2node[stanza.attr.id];
133 local identifier = id2identifier[stanza.attr.id]; 129 local identifier = id2identifier[stanza.attr.id];
134 if node == nil then return false; end -- unknown stanza? Ignore for now! 130 if node == nil then return false; end -- unknown stanza? Ignore for now!
135 local from = stanza.attr.from; 131 local from = stanza.attr.from;
136 local user_push_services = push_store:get(node); 132 local user_push_services = push_store:get(node);
137 133
138 for push_identifier, _ in pairs(user_push_services) do 134 for push_identifier, _ in pairs(user_push_services) do
139 if push_identifier == identifier then 135 if push_identifier == identifier then
140 if user_push_services[push_identifier] and user_push_services[push_identifier].jid == from and push_errors[push_identifier] > 0 then 136 if user_push_services[push_identifier] and user_push_services[push_identifier].jid == from and push_errors[push_identifier] > 0 then
141 push_errors[push_identifier] = 0; 137 push_errors[push_identifier] = 0;
142 -- unhook iq handlers for this identifier (if possible) 138 -- unhook iq handlers for this identifier (if possible)
143 module:unhook("iq-error/host/"..stanza.attr.id, handle_push_error); 139 module:unhook("iq-error/host/"..stanza.attr.id, handle_push_error);
144 module:unhook("iq-result/host/"..stanza.attr.id, handle_push_success); 140 module:unhook("iq-result/host/"..stanza.attr.id, handle_push_success);
145 id2node[stanza.attr.id] = nil; 141 id2node[stanza.attr.id] = nil;
146 id2identifier[stanza.attr.id] = nil; 142 id2identifier[stanza.attr.id] = nil;
147 module:log("debug", "Push succeeded, error count for identifier '%s' is now at %s again", push_identifier, tostring(push_errors[push_identifier])); 143 module:log("debug", "Push succeeded, error count for identifier '%s' is now at %s again",
144 push_identifier, tostring(push_errors[push_identifier])
145 );
148 end 146 end
149 end 147 end
150 end 148 end
151 return true; 149 return true;
152 end 150 end
247 module:hook("iq-set/self/"..xmlns_push..":disable", push_disable); 245 module:hook("iq-set/self/"..xmlns_push..":disable", push_disable);
248 246
249 -- is this push a high priority one (this is needed for ios apps not using voip pushes) 247 -- is this push a high priority one (this is needed for ios apps not using voip pushes)
250 local function is_important(stanza) 248 local function is_important(stanza)
251 local st_name = stanza and stanza.name or nil; 249 local st_name = stanza and stanza.name or nil;
252 if not st_name then return false; end -- nonzas are never important here 250 if not st_name then return false; end -- nonzas are never important here
253 if st_name == "presence" then 251 if st_name == "presence" then
254 return false; -- same for presences 252 return false; -- same for presences
255 elseif st_name == "message" then 253 elseif st_name == "message" then
256 -- unpack carbon copies 254 -- unpack carbon copied message stanzas
257 local stanza_direction = "in"; 255 local carbon = stanza:find("{urn:xmpp:carbons:2}/{urn:xmpp:forward:0}/{jabber:client}message");
258 local carbon; 256 local stanza_direction = carbon and stanza:child_with_name("sent") and "out" or "in";
259 local st_type;
260 -- support carbon copied message stanzas
261 if not carbon then carbon = stanza:find("{urn:xmpp:carbons:2}/{urn:xmpp:forward:0}/{jabber:client}message"); end
262 stanza_direction = carbon and stanza:child_with_name("sent") and "out" or "in";
263 if carbon then stanza = carbon; end 257 if carbon then stanza = carbon; end
264 st_type = stanza.attr.type; 258 local st_type = stanza.attr.type;
265 259
266 -- headline message are always not important 260 -- headline message are always not important
267 if st_type == "headline" then return false; end 261 if st_type == "headline" then return false; end
268 262
269 -- carbon copied outgoing messages are not important 263 -- carbon copied outgoing messages are not important
270 if carbon and stanza_direction == "out" then return false; end 264 if carbon and stanza_direction == "out" then return false; end
271 265
272 -- We can't check for body contents in encrypted messages, so let's treat them as important 266 -- We can't check for body contents in encrypted messages, so let's treat them as important
273 -- Some clients don't even set a body or an empty body for encrypted messages 267 -- Some clients don't even set a body or an empty body for encrypted messages
274 268
275 -- check omemo https://xmpp.org/extensions/inbox/omemo.html 269 -- check omemo https://xmpp.org/extensions/inbox/omemo.html
276 if stanza:get_child("encrypted", "eu.siacs.conversations.axolotl") or stanza:get_child("encrypted", "urn:xmpp:omemo:0") then return true; end 270 if stanza:get_child("encrypted", "eu.siacs.conversations.axolotl") or stanza:get_child("encrypted", "urn:xmpp:omemo:0") then return true; end
277 271
278 -- check xep27 pgp https://xmpp.org/extensions/xep-0027.html 272 -- check xep27 pgp https://xmpp.org/extensions/xep-0027.html
279 if stanza:get_child("x", "jabber:x:encrypted") then return true; end 273 if stanza:get_child("x", "jabber:x:encrypted") then return true; end
280 274
281 -- check xep373 pgp (OX) https://xmpp.org/extensions/xep-0373.html 275 -- check xep373 pgp (OX) https://xmpp.org/extensions/xep-0373.html
282 if stanza:get_child("openpgp", "urn:xmpp:openpgp:0") then return true; end 276 if stanza:get_child("openpgp", "urn:xmpp:openpgp:0") then return true; end
283 277
284 local body = stanza:get_child_text("body"); 278 local body = stanza:get_child_text("body");
285 if st_type == "groupchat" and stanza:get_child_text("subject") then return false; end -- groupchat subjects are not important here 279
286 return body ~= nil and body ~= ""; -- empty bodies are not important 280 -- groupchat subjects are not important here
281 if st_type == "groupchat" and stanza:get_child_text("subject") then
282 return false;
283 end
284
285 -- empty bodies are not important
286 return body ~= nil and body ~= "";
287 end 287 end
288 return false; -- this stanza wasn't one of the above cases --> it is not important, too 288 return false; -- this stanza wasn't one of the above cases --> it is not important, too
289 end 289 end
290 290
291 local push_form = dataform { 291 local push_form = dataform {
298 298
299 -- http://xmpp.org/extensions/xep-0357.html#publishing 299 -- http://xmpp.org/extensions/xep-0357.html#publishing
300 local function handle_notify_request(stanza, node, user_push_services, log_push_decline) 300 local function handle_notify_request(stanza, node, user_push_services, log_push_decline)
301 local pushes = 0; 301 local pushes = 0;
302 if not #user_push_services then return pushes end 302 if not #user_push_services then return pushes end
303 303
304 for push_identifier, push_info in pairs(user_push_services) do 304 for push_identifier, push_info in pairs(user_push_services) do
305 local send_push = true; -- only send push to this node when not already done for this stanza or if no stanza is given at all 305 local send_push = true; -- only send push to this node when not already done for this stanza or if no stanza is given at all
306 if stanza then 306 if stanza then
307 if not stanza._push_notify then stanza._push_notify = {}; end 307 if not stanza._push_notify then stanza._push_notify = {}; end
308 if stanza._push_notify[push_identifier] then 308 if stanza._push_notify[push_identifier] then
311 end 311 end
312 send_push = false; 312 send_push = false;
313 end 313 end
314 stanza._push_notify[push_identifier] = true; 314 stanza._push_notify[push_identifier] = true;
315 end 315 end
316 316
317 if send_push then 317 if send_push then
318 -- construct push stanza 318 -- construct push stanza
319 local stanza_id = hashes.sha256(random.bytes(8), true); 319 local stanza_id = hashes.sha256(random.bytes(8), true);
320 local push_publish = st.iq({ to = push_info.jid, from = module.host, type = "set", id = stanza_id }) 320 local push_publish = st.iq({ to = push_info.jid, from = module.host, type = "set", id = stanza_id })
321 :tag("pubsub", { xmlns = "http://jabber.org/protocol/pubsub" }) 321 :tag("pubsub", { xmlns = "http://jabber.org/protocol/pubsub" })
340 push_publish:up(); -- / pubsub 340 push_publish:up(); -- / pubsub
341 if push_info.options then 341 if push_info.options then
342 push_publish:tag("publish-options"):add_child(st.deserialize(push_info.options)); 342 push_publish:tag("publish-options"):add_child(st.deserialize(push_info.options));
343 end 343 end
344 -- send out push 344 -- send out push
345 module:log("debug", "Sending %s push notification for %s@%s to %s (%s)", form_data["last-message-body"] and "important" or "unimportant", node, module.host, push_info.jid, tostring(push_info.node)); 345 module:log("debug", "Sending %s push notification for %s@%s to %s (%s)",
346 form_data["last-message-body"] and "important" or "unimportant",
347 node, module.host, push_info.jid, tostring(push_info.node)
348 );
346 -- module:log("debug", "PUSH STANZA: %s", tostring(push_publish)); 349 -- module:log("debug", "PUSH STANZA: %s", tostring(push_publish));
347 -- handle push errors for this node 350 -- handle push errors for this node
348 if push_errors[push_identifier] == nil then 351 if push_errors[push_identifier] == nil then
349 push_errors[push_identifier] = 0; 352 push_errors[push_identifier] = 0;
350 end 353 end
381 for i=1, #queue do 384 for i=1, #queue do
382 local stanza = queue[i]; 385 local stanza = queue[i];
383 -- fast ignore of already pushed stanzas 386 -- fast ignore of already pushed stanzas
384 if stanza and not (stanza._push_notify and stanza._push_notify[session.push_identifier]) then 387 if stanza and not (stanza._push_notify and stanza._push_notify[session.push_identifier]) then
385 local node = get_push_settings(stanza, session); 388 local node = get_push_settings(stanza, session);
386 stanza_type = "unimportant" 389 local stanza_type = "unimportant";
387 if dummy_body and is_important(stanza) then stanza_type = "important"; end 390 if dummy_body and is_important(stanza) then stanza_type = "important"; end
388 if not notified[stanza_type] then -- only notify if we didn't try to push for this stanza type already 391 if not notified[stanza_type] then -- only notify if we didn't try to push for this stanza type already
389 -- session.log("debug", "Invoking cloud handle_notify_request() for smacks queued stanza: %d", i); 392 -- session.log("debug", "Invoking cloud handle_notify_request() for smacks queued stanza: %d", i);
390 if handle_notify_request(stanza, node, user_push_services, false) ~= 0 then 393 if handle_notify_request(stanza, node, user_push_services, false) ~= 0 then
391 if session.hibernating and not session.first_hibernated_push then 394 if session.hibernating and not session.first_hibernated_push then
422 process_stanza_queue(session.push_queue, session, "push"); 425 process_stanza_queue(session.push_queue, session, "push");
423 session.push_queue = {}; -- clean up queue after push 426 session.push_queue = {}; -- clean up queue after push
424 end); 427 end);
425 end 428 end
426 else 429 else
427 session.log("debug", "NOT invoking cloud handle_notify_request() for newly smacks queued stanza (session.push_identifier is not set: %s)", session.push_identifier); 430 session.log("debug", "NOT invoking cloud handle_notify_request() for newly smacks queued stanza (session.push_identifier is not set: %s)",
431 session.push_identifier
432 );
428 end 433 end
429 return stanza; 434 return stanza;
430 end 435 end
431 436
432 -- smacks hibernation is started 437 -- smacks hibernation is started
466 to = to and jid.split(to) or event.origin.username; 471 to = to and jid.split(to) or event.origin.username;
467 472
468 -- only notify if the stanza destination is the mam user we store it for 473 -- only notify if the stanza destination is the mam user we store it for
469 if event.for_user == to then 474 if event.for_user == to then
470 local user_push_services = push_store:get(to); 475 local user_push_services = push_store:get(to);
471 476
472 -- only notify nodes with no active sessions (smacks is counted as active and handled separate) 477 -- only notify nodes with no active sessions (smacks is counted as active and handled separate)
473 local notify_push_services = {}; 478 local notify_push_services = {};
474 for identifier, push_info in pairs(user_push_services) do 479 for identifier, push_info in pairs(user_push_services) do
475 local identifier_found = nil; 480 local identifier_found = nil;
476 for _, session in pairs(user_session) do 481 for _, session in pairs(user_session) do
477 -- module:log("debug", "searching for '%s': identifier '%s' for session %s", tostring(identifier), tostring(session.push_identifier), tostring(session.full_jid));
478 if session.push_identifier == identifier then 482 if session.push_identifier == identifier then
479 identifier_found = session; 483 identifier_found = session;
480 break; 484 break;
481 end 485 end
482 end 486 end
510 module:log("info", "Module loaded"); 514 module:log("info", "Module loaded");
511 function module.unload() 515 function module.unload()
512 module:log("info", "Unloading module"); 516 module:log("info", "Unloading module");
513 -- cleanup some settings, reloading this module can cause process_smacks_stanza() to stop working otherwise 517 -- cleanup some settings, reloading this module can cause process_smacks_stanza() to stop working otherwise
514 for user, _ in pairs(host_sessions) do 518 for user, _ in pairs(host_sessions) do
515 for sessionid, session in pairs(host_sessions[user].sessions) do 519 for _, session in pairs(host_sessions[user].sessions) do
516 if session.awaiting_push_timer then session.awaiting_push_timer:stop(); end 520 if session.awaiting_push_timer then session.awaiting_push_timer:stop(); end
517 session.awaiting_push_timer = nil; 521 session.awaiting_push_timer = nil;
518 session.first_hibernated_push = nil; 522 session.first_hibernated_push = nil;
519 session.push_queue = nil; 523 session.push_queue = nil;
520 end 524 end