plugins/mod_tombstones.lua
branch0.12
changeset 12442 a698f65df453
parent 12121 0c9b64178eda
child 12981 74b9e05af71e
equal deleted inserted replaced
12441:9f5d0b77e3df 12442:a698f65df453
     1 -- TODO warn when trying to create an user before the tombstone expires
     1 -- TODO warn when trying to create an user before the tombstone expires
     2 -- e.g. via telnet or other admin interface
     2 -- e.g. via telnet or other admin interface
     3 local datetime = require "util.datetime";
     3 local datetime = require "util.datetime";
     4 local errors = require "util.error";
     4 local errors = require "util.error";
     5 local jid_split = require"util.jid".split;
     5 local jid_node = require"util.jid".node;
     6 local st = require "util.stanza";
     6 local st = require "util.stanza";
     7 
     7 
     8 -- Using a map store as key-value store so that removal of all user data
     8 -- Using a map store as key-value store so that removal of all user data
     9 -- does not also remove the tombstone, which would defeat the point
     9 -- does not also remove the tombstone, which would defeat the point
    10 local graveyard = module:open_store(nil, "map");
    10 local graveyard = module:open_store(nil, "map");
       
    11 local graveyard_cache = require "util.cache".new(module:get_option_number("tombstone_cache_size", 1024));
    11 
    12 
    12 local ttl = module:get_option_number("user_tombstone_expiry", nil);
    13 local ttl = module:get_option_number("user_tombstone_expiry", nil);
    13 -- Keep tombstones forever by default
    14 -- Keep tombstones forever by default
    14 --
    15 --
    15 -- Rationale:
    16 -- Rationale:
    27 	end
    28 	end
    28 end);
    29 end);
    29 
    30 
    30 -- Public API
    31 -- Public API
    31 function has_tombstone(username)
    32 function has_tombstone(username)
    32 	local tombstone, err = graveyard:get(nil, username);
    33 	local tombstone;
    33 
    34 
    34 	if err or not tombstone then return tombstone, err; end
    35 	-- Check cache
       
    36 	local cached_result = graveyard_cache:get(username);
       
    37 	if cached_result == false then
       
    38 		-- We cached that there is no tombstone for this user
       
    39 		return false;
       
    40 	elseif cached_result then
       
    41 		tombstone = cached_result;
       
    42 	else
       
    43 		local stored_result, err = graveyard:get(nil, username);
       
    44 		if not stored_result and not err then
       
    45 			-- Cache that there is no tombstone for this user
       
    46 			graveyard_cache:set(username, false);
       
    47 			return false;
       
    48 		elseif err then
       
    49 			-- Failed to check tombstone status
       
    50 			return nil, err;
       
    51 		end
       
    52 		-- We have a tombstone stored, so let's continue with that
       
    53 		tombstone = stored_result;
       
    54 	end
    35 
    55 
       
    56 	-- Check expiry
    36 	if ttl and tombstone + ttl < os.time() then
    57 	if ttl and tombstone + ttl < os.time() then
    37 		module:log("debug", "Tombstone for %s created at %s has expired", username, datetime.datetime(tombstone));
    58 		module:log("debug", "Tombstone for %s created at %s has expired", username, datetime.datetime(tombstone));
    38 		graveyard:set(nil, username, nil);
    59 		graveyard:set(nil, username, nil);
       
    60 		graveyard_cache:set(username, nil); -- clear cache entry (if any)
    39 		return nil;
    61 		return nil;
    40 	end
    62 	end
       
    63 
       
    64 	-- Cache for the future
       
    65 	graveyard_cache:set(username, tombstone);
       
    66 
    41 	return tombstone;
    67 	return tombstone;
    42 end
    68 end
    43 
    69 
    44 module:hook("user-registering", function(event)
    70 module:hook("user-registering", function(event)
    45 	local tombstone, err = has_tombstone(event.username);
    71 	local tombstone, err = has_tombstone(event.username);
    57 	return true;
    83 	return true;
    58 end);
    84 end);
    59 
    85 
    60 module:hook("presence/bare", function(event)
    86 module:hook("presence/bare", function(event)
    61 	local origin, presence = event.origin, event.stanza;
    87 	local origin, presence = event.origin, event.stanza;
       
    88 	local local_username = jid_node(presence.attr.to);
       
    89 	if not local_username then return; end
    62 
    90 
    63 	-- We want to undo any left-over presence subscriptions and notify the former
    91 	-- We want to undo any left-over presence subscriptions and notify the former
    64 	-- contact that they're gone.
    92 	-- contact that they're gone.
    65 	--
    93 	--
    66 	-- FIXME This leaks that the user once existed. Hard to avoid without keeping
    94 	-- FIXME This leaks that the user once existed. Hard to avoid without keeping
    67 	-- the contact list in some form, which we don't want to do for privacy
    95 	-- the contact list in some form, which we don't want to do for privacy
    68 	-- reasons.  Bloom filter perhaps?
    96 	-- reasons.  Bloom filter perhaps?
    69 	if has_tombstone(jid_split(presence.attr.to)) then
    97 
    70 		if presence.attr.type == "probe" then
    98 	local pres_type = presence.attr.type;
    71 			origin.send(st.error_reply(presence, "cancel", "gone", "User deleted"));
    99 	local is_probe = pres_type == "probe";
    72 			origin.send(st.presence({ type = "unsubscribed"; to = presence.attr.from; from = presence.attr.to }));
   100 	local is_normal = pres_type == nil or pres_type == "unavailable";
    73 		elseif presence.attr.type == nil or presence.attr.type == "unavailable" then
   101 	if is_probe and has_tombstone(local_username) then
    74 			origin.send(st.error_reply(presence, "cancel", "gone", "User deleted"));
   102 		origin.send(st.error_reply(presence, "cancel", "gone", "User deleted"));
    75 			origin.send(st.presence({ type = "unsubscribe"; to = presence.attr.from; from = presence.attr.to }));
   103 		origin.send(st.presence({ type = "unsubscribed"; to = presence.attr.from; from = presence.attr.to }));
    76 		end
   104 		return true;
       
   105 	elseif is_normal and has_tombstone(local_username) then
       
   106 		origin.send(st.error_reply(presence, "cancel", "gone", "User deleted"));
       
   107 		origin.send(st.presence({ type = "unsubscribe"; to = presence.attr.from; from = presence.attr.to }));
    77 		return true;
   108 		return true;
    78 	end
   109 	end
    79 end, 1);
   110 end, 1);